mirror of
https://github.com/Jellify-Music/App.git
synced 2026-01-08 03:49:59 -06:00
Merge branch 'main' of github.com:anultravioletaurora/Jellify
This commit is contained in:
4
.github/workflows/build-android.yml
vendored
4
.github/workflows/build-android.yml
vendored
@@ -16,8 +16,8 @@ jobs:
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- name: 🧵 Run yarn install
|
||||
run: yarn install
|
||||
- name: 🤖 Run yarn init-android
|
||||
run: yarn init-android
|
||||
|
||||
- name: 🚀 Run fastlane build
|
||||
run: yarn fastlane:android:build
|
||||
|
||||
5
.github/workflows/build-ios.yml
vendored
5
.github/workflows/build-ios.yml
vendored
@@ -16,13 +16,10 @@ jobs:
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: 🍎 Run yarn init-ios:new-arch
|
||||
run: yarn init-ios:new-arch
|
||||
|
||||
- name: 🍫 Install CocoaPods
|
||||
run: yarn pod:install
|
||||
|
||||
- name: 🚀 Run fastlane build
|
||||
run: yarn fastlane:ios:build
|
||||
env:
|
||||
|
||||
13
.github/workflows/publish-beta.yml
vendored
13
.github/workflows/publish-beta.yml
vendored
@@ -16,24 +16,23 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
- name: 🍎 Run yarn init-ios:new-arch
|
||||
run: yarn init-ios:new-arch
|
||||
|
||||
|
||||
- name: ➕ Version Up
|
||||
run: yarn react-native bump-version --type patch
|
||||
|
||||
|
||||
|
||||
- name: 💬 Echo package.json version to Github ENV
|
||||
run: echo VERSION_NUMBER=$(node -p -e "require('./package.json').version") >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: 🤫 Output App Store Connect API Key JSON to Fastlane
|
||||
run: echo -e '${{ secrets.APPSTORE_CONNECT_API_KEY_JSON }}' > appstore_connect_api_key.json
|
||||
working-directory: ./ios/fastlane
|
||||
|
||||
|
||||
- name: 🚀 Run Android fastlane build
|
||||
run: yarn fastlane:android:build
|
||||
|
||||
|
||||
- name: 🚀 Run iOS fastlane build and publish to TestFlight
|
||||
run: yarn fastlane:ios:beta
|
||||
env:
|
||||
|
||||
5
.github/workflows/run-jest-test-suite.yml
vendored
5
.github/workflows/run-jest-test-suite.yml
vendored
@@ -22,7 +22,10 @@ jobs:
|
||||
- name: 🍎 Run yarn init-ios:new-arch
|
||||
run: yarn init-ios:new-arch
|
||||
|
||||
- name: 🧪 Run npm test
|
||||
- name: 🔍 Run yarn tsc
|
||||
run: yarn tsc
|
||||
|
||||
- name: 🧪 Run yarn test
|
||||
run: yarn test
|
||||
|
||||
- name: 🦋 Check Styling
|
||||
|
||||
17
App.tsx
17
App.tsx
@@ -2,21 +2,23 @@ import './gesture-handler'
|
||||
import React, { useState } from 'react'
|
||||
import 'react-native-url-polyfill/auto'
|
||||
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
||||
import Jellify from './components/jellify'
|
||||
import Jellify from './src/components/jellify'
|
||||
import { TamaguiProvider, Theme } from 'tamagui'
|
||||
import { useColorScheme } from 'react-native'
|
||||
import jellifyConfig from './tamagui.config'
|
||||
import { clientPersister } from './constants/storage'
|
||||
import { queryClient } from './constants/query-client'
|
||||
import { clientPersister } from './src/constants/storage'
|
||||
import { queryClient } from './src/constants/query-client'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import TrackPlayer, { IOSCategory, IOSCategoryOptions } from 'react-native-track-player'
|
||||
import { CAPABILITIES } from './player/constants'
|
||||
import { CAPABILITIES } from './src/player/constants'
|
||||
import { createWorkletRuntime } from 'react-native-reanimated'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import { NavigationContainer } from '@react-navigation/native'
|
||||
import { JellifyDarkTheme, JellifyLightTheme } from './components/theme'
|
||||
import { requestStoragePermission } from './helpers/permisson-helpers'
|
||||
import ErrorBoundary from './components/ErrorBoundary'
|
||||
import { JellifyDarkTheme, JellifyLightTheme } from './src/components/theme'
|
||||
import { requestStoragePermission } from './src/helpers/permisson-helpers'
|
||||
import ErrorBoundary from './src/components/ErrorBoundary'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import JellifyToastConfig from './src/constants/toast.config'
|
||||
|
||||
export const backgroundRuntime = createWorkletRuntime('background')
|
||||
|
||||
@@ -84,6 +86,7 @@ export default function App(): React.JSX.Element {
|
||||
</TamaguiProvider>
|
||||
</GestureHandlerRootView>
|
||||
</PersistQueryClientProvider>
|
||||
<Toast config={JellifyToastConfig(isDarkMode)} />
|
||||
</NavigationContainer>
|
||||
</ErrorBoundary>
|
||||
</SafeAreaProvider>
|
||||
|
||||
304
Gemfile.lock
304
Gemfile.lock
@@ -1,304 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
activesupport (7.2.2.1)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.1044.0)
|
||||
aws-sdk-core (3.217.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.97.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.179.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.2.0)
|
||||
benchmark (0.4.0)
|
||||
bigdecimal (3.1.9)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.15.2)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.15.2)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 2.1, < 3.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.23.0, < 2.0)
|
||||
cocoapods-core (1.15.2)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (2.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.3.3)
|
||||
connection_pool (2.5.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.6.20240107)
|
||||
dotenv (2.8.1)
|
||||
drb (2.2.1)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.112.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.1.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.226.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.4.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
ffi (1.17.1)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.31.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.7.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.4.0)
|
||||
google-cloud-storage (1.47.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.31.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.8.1)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.9.1)
|
||||
jwt (2.10.1)
|
||||
base64
|
||||
logger (1.6.5)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.4)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.4.1)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.2)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.2.1)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.4.0)
|
||||
rouge (3.28.0)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.4.1)
|
||||
securerandom (0.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.19.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.25.1)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.0)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
activesupport (>= 6.1.7.5, != 7.1.0)
|
||||
cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
|
||||
concurrent-ruby (< 1.3.4)
|
||||
fastlane
|
||||
xcodeproj (< 1.26.0)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.1.4p223
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.26
|
||||
13
README.md
13
README.md
@@ -2,14 +2,15 @@
|
||||
|
||||

|
||||
|
||||
[](https://github.com/anultravioletaurora/Jellify/actions/workflows/publish-beta.yml)
|
||||
[](https://github.com/anultravioletaurora/Jellify/releases)
|
||||
[](https://github.com/anultravioletaurora/Jellify/actions/workflows/publish-beta.yml) [](https://github.com/sponsors/anultravioletaurora)
|
||||
|
||||
## 🔗 Quick Links
|
||||
|
||||
[Discord Server](https://discord.gg/yf8fBatktn)
|
||||
|
||||
[TestFlight](https://testflight.apple.com/join/etVSc7ZQ)
|
||||
|
||||
[](https://discord.gg/yf8fBatktn)
|
||||
|
||||
## ℹ️ About
|
||||
|
||||
> **jellify** (verb) - _to make gelatinous_ <br>
|
||||
@@ -114,26 +115,24 @@ Playlist
|
||||
|
||||
## 🏗 Built with good stuff
|
||||
|
||||
[](https://reactjs.org) [](https://typescriptlang.org) [](https://github.com/prettier/prettier)
|
||||
[](https://reactjs.org) [](https://reactnative.dev) [](https://typescriptlang.org) [](https://github.com/prettier/prettier) [](https://github.com/anultravioletaurora/jellify/blob/main/LICENSE)
|
||||
|
||||
### 🎨 Frontend
|
||||
|
||||
[Tamagui](https://tamagui.dev/)\
|
||||
[Burnt](https://github.com/nandorojo/burnt)\
|
||||
[React Navigation](https://reactnavigation.org/)\
|
||||
[React Native CarPlay](https://github.com/birkir/react-native-carplay)\
|
||||
[React Native Draggable Flatlist](https://github.com/computerjazz/react-native-draggable-flatlist)\
|
||||
[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)\
|
||||
[React Native Toast Message](https://github.com/calintamas/react-native-toast-message)\
|
||||
[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)
|
||||
|
||||
### 🎛️ 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)\
|
||||
[React Native Boost](https://github.com/kuatsu/react-native-boost)\
|
||||
[React Native File Access](https://github.com/alpha0010/react-native-file-access)\
|
||||
[React Native MMKV](https://github.com/mrousavy/react-native-mmkv)\
|
||||
[React Native Track Player](https://github.com/doublesymmetry/react-native-track-player)\
|
||||
|
||||
@@ -96,8 +96,8 @@ android {
|
||||
applicationId "com.jellify"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 40
|
||||
versionName "0.11.15"
|
||||
versionCode 46
|
||||
versionName "0.11.21"
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package com.jellify
|
||||
import expo.modules.ReactActivityDelegateWrapper
|
||||
|
||||
|
||||
import com.facebook.react.ReactActivity
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
@@ -19,5 +19,6 @@ class MainActivity : ReactActivity() {
|
||||
* which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
|
||||
*/
|
||||
override fun createReactActivityDelegate(): ReactActivityDelegate =
|
||||
ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled))
|
||||
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
package com.jellify
|
||||
import android.content.res.Configuration
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.react.PackageList
|
||||
@@ -18,7 +15,7 @@ import com.facebook.soloader.SoLoader
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val reactNativeHost: ReactNativeHost =
|
||||
ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) {
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
@@ -31,10 +28,10 @@ class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
})
|
||||
}
|
||||
|
||||
override val reactHost: ReactHost
|
||||
get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
|
||||
get() = getDefaultReactHost(applicationContext, reactNativeHost)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -43,11 +40,5 @@ class MainApplication : Application(), ReactApplication {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
}
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,6 @@
|
||||
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
|
||||
plugins { id("com.facebook.react.settings") }
|
||||
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex ->
|
||||
def command = [
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
'android'
|
||||
].toList()
|
||||
ex.autolinkLibrariesFromCommand(command)
|
||||
}
|
||||
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
|
||||
rootProject.name = 'Jellify'
|
||||
include ':app'
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
|
||||
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
|
||||
useExpoModules()
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { BaseItemKind, ItemSortBy, SortOrder } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import Client from '../client'
|
||||
|
||||
export const useArtistFeaturedOnAlbums = (artistId: string) =>
|
||||
useQuery({
|
||||
queryKey: [QueryKeys.ArtistFeaturedAlbums, artistId],
|
||||
queryFn: ({ queryKey }) => {
|
||||
return getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
includeItemTypes: [BaseItemKind.MusicAlbum],
|
||||
recursive: true,
|
||||
excludeItemIds: [queryKey[1] as string],
|
||||
sortBy: [
|
||||
ItemSortBy.PremiereDate,
|
||||
ItemSortBy.ProductionYear,
|
||||
ItemSortBy.SortName,
|
||||
],
|
||||
sortOrder: [SortOrder.Descending],
|
||||
contributingArtistIds: [queryKey[1] as string],
|
||||
})
|
||||
.then((response) => {
|
||||
return response.data.Items ? response.data.Items! : []
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Dirs, FileSystem } from 'react-native-file-access'
|
||||
import Client from '../../../api/client'
|
||||
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
export async function downloadTrack(itemId: string): Promise<void> {
|
||||
// Make sure downloads folder exists, create if it doesn't
|
||||
if (!(await FileSystem.exists(`${Dirs.DocumentDir}/downloads`)))
|
||||
await FileSystem.mkdir(`${Dirs.DocumentDir}/downloads`)
|
||||
|
||||
getLibraryApi(Client.api!)
|
||||
.getDownload(
|
||||
{
|
||||
itemId,
|
||||
},
|
||||
{
|
||||
responseType: 'blob',
|
||||
},
|
||||
)
|
||||
.then(async (response) => {
|
||||
if (response.status < 300) {
|
||||
await FileSystem.writeFile(getTrackFilePath(itemId), response.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getTrackFilePath(itemId: string) {
|
||||
return `${Dirs.DocumentDir}/downloads/${itemId}`
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import Client from '../../../api/client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
export async function fetchItem(itemId: string): Promise<BaseItemDto> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isEmpty(itemId)) reject('No item ID proviced')
|
||||
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
ids: [itemId],
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data.Items && response.data.TotalRecordCount == 1)
|
||||
resolve(response.data.Items[0])
|
||||
else reject(`${response.data.TotalRecordCount} items returned for ID`)
|
||||
})
|
||||
})
|
||||
}
|
||||
5
app.json
5
app.json
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"name": "Jellify",
|
||||
"displayName": "Jellify",
|
||||
"expo": {
|
||||
"newArchEnabled": true
|
||||
}
|
||||
"displayName": "Jellify"
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
import { HomeAlbumProps } from '../types'
|
||||
import { YStack, XStack, Separator, getToken } from 'tamagui'
|
||||
import { BaseItemDto, ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { H5, Text } from '../Global/helpers/text'
|
||||
import { FlatList, SectionList } from 'react-native'
|
||||
import { RunTimeTicks } from '../Global/helpers/time-codes'
|
||||
import Track from '../Global/components/track'
|
||||
import FavoriteButton from '../Global/components/favorite-button'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { getImageApi, getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import Client from '../../api/client'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { Image } from 'expo-image'
|
||||
import { groupBy, isEqual } from 'lodash'
|
||||
|
||||
export function AlbumScreen({ route, navigation }: HomeAlbumProps): React.JSX.Element {
|
||||
const { album } = route.params
|
||||
|
||||
navigation.setOptions({
|
||||
headerRight: () => {
|
||||
return <FavoriteButton item={album} />
|
||||
},
|
||||
})
|
||||
|
||||
const { data: discs } = useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, album.Id!],
|
||||
queryFn: () => {
|
||||
let sortBy: ItemSortBy[] = []
|
||||
|
||||
sortBy = [ItemSortBy.ParentIndexNumber, ItemSortBy.IndexNumber, ItemSortBy.SortName]
|
||||
|
||||
return new Promise<{ title: string; data: BaseItemDto[] }[]>((resolve, reject) => {
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
parentId: album.Id!,
|
||||
sortBy,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const discs = data.Items
|
||||
? Object.keys(
|
||||
groupBy(data.Items, (track) => track.ParentIndexNumber),
|
||||
).map((discNumber) => {
|
||||
console.debug(discNumber)
|
||||
return {
|
||||
title: discNumber,
|
||||
data: data.Items!.filter((track: BaseItemDto) =>
|
||||
track.ParentIndexNumber
|
||||
? isEqual(
|
||||
discNumber,
|
||||
(track.ParentIndexNumber ?? 0).toString(),
|
||||
)
|
||||
: track,
|
||||
),
|
||||
}
|
||||
})
|
||||
: [{ title: '1', data: [] }]
|
||||
|
||||
resolve(discs)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
sections={discs ? discs : [{ title: '1', data: [] }]}
|
||||
keyExtractor={(item, index) => item.Id! + index}
|
||||
ItemSeparatorComponent={() => <Separator />}
|
||||
renderSectionHeader={({ section }) => {
|
||||
return discs && discs.length >= 2 ? (
|
||||
<Text
|
||||
paddingVertical={'$2'}
|
||||
paddingLeft={'$4.5'}
|
||||
backgroundColor={'$background'}
|
||||
bold
|
||||
>{`Disc ${section.title}`}</Text>
|
||||
) : null
|
||||
}}
|
||||
ListHeaderComponent={
|
||||
<YStack marginTop={'$2'} minHeight={getToken('$20') + getToken('$15')}>
|
||||
<Image
|
||||
source={getImageApi(Client.api!).getItemImageUrlById(album.Id!)}
|
||||
style={{
|
||||
borderRadius: getToken('$5'),
|
||||
width: getToken('$20') + getToken('$15'),
|
||||
height: getToken('$20') + getToken('$15'),
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
<H5 textAlign='center'>{album.Name ?? 'Untitled Album'}</H5>
|
||||
|
||||
<XStack justifyContent='space-evenly'>
|
||||
<Text>{album.ProductionYear?.toString() ?? ''}</Text>
|
||||
</XStack>
|
||||
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
marginLeft: 2,
|
||||
}}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
horizontal
|
||||
keyExtractor={(item) => item.Id!}
|
||||
data={album.ArtistItems}
|
||||
renderItem={({ index, item: artist }) => (
|
||||
<ItemCard
|
||||
size={'$8'}
|
||||
item={artist}
|
||||
caption={artist.Name ?? 'Unknown Artist'}
|
||||
onPress={() => {
|
||||
navigation.navigate('Artist', {
|
||||
artist,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</YStack>
|
||||
}
|
||||
renderItem={({ item: track, index }) => (
|
||||
<Track
|
||||
track={track}
|
||||
tracklist={discs?.flatMap((disc) => disc.data)}
|
||||
index={discs?.flatMap((disc) => disc.data).indexOf(track) ?? index}
|
||||
navigation={navigation}
|
||||
queue={album}
|
||||
/>
|
||||
)}
|
||||
ListFooterComponent={
|
||||
<XStack marginRight={'$2'} justifyContent='flex-end'>
|
||||
<Text color={'$purpleGray'} paddingRight={'$1'}>
|
||||
Total Runtime:
|
||||
</Text>
|
||||
<RunTimeTicks>{album.RunTimeTicks}</RunTimeTicks>
|
||||
</XStack>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import { BaseItemDto, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import { Square, View } from 'tamagui'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { Image } from 'react-native'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchItemImage } from '../../../api/queries/functions/images'
|
||||
|
||||
interface BlurhashLoadingProps {
|
||||
item: BaseItemDto
|
||||
width: number
|
||||
height?: number
|
||||
type?: ImageType
|
||||
borderRadius?: number | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* Please use the `Image` component from
|
||||
* the `expo-image` module instead, as that is more performant
|
||||
*
|
||||
* A React component that will render a Blurhash
|
||||
* string as an image while loading the full image
|
||||
* from the server
|
||||
*
|
||||
* Image Query is stale after 30 minutes and collected
|
||||
* after an hour to keep the cache size down and the
|
||||
* app performant
|
||||
*
|
||||
* TODO: Keep images in offline mode
|
||||
*
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
export default function BlurhashedImage({
|
||||
item,
|
||||
width,
|
||||
height,
|
||||
type,
|
||||
borderRadius,
|
||||
}: BlurhashLoadingProps): React.JSX.Element {
|
||||
const { data: image, isSuccess } = useQuery({
|
||||
queryKey: [
|
||||
QueryKeys.ItemImage,
|
||||
item.AlbumId ? item.AlbumId : item.Id!,
|
||||
type ?? ImageType.Primary,
|
||||
Math.ceil(width / 100) * 100, // Images are fetched at a higher, generic resolution
|
||||
Math.ceil(height ?? width / 100) * 100, // So these keys need to match
|
||||
],
|
||||
queryFn: () =>
|
||||
fetchItemImage(
|
||||
item.AlbumId ? item.AlbumId : item.Id!,
|
||||
type ?? ImageType.Primary,
|
||||
width,
|
||||
height ?? width,
|
||||
),
|
||||
staleTime: 1000 * 60 * 30, // 30 minutes
|
||||
gcTime: 1000 * 60 * 60, // 1 hour
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
const blurhash =
|
||||
!isEmpty(item.ImageBlurHashes) &&
|
||||
!isEmpty(type ? item.ImageBlurHashes[type] : item.ImageBlurHashes.Primary)
|
||||
? Object.values(type ? item.ImageBlurHashes[type]! : item.ImageBlurHashes.Primary!)[0]
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<View
|
||||
minHeight={height ?? width}
|
||||
minWidth={width}
|
||||
borderRadius={borderRadius ? borderRadius : 25}
|
||||
>
|
||||
{isSuccess ? (
|
||||
<Image
|
||||
source={{
|
||||
uri: image ?? undefined,
|
||||
}}
|
||||
style={{
|
||||
height: height ?? width,
|
||||
width,
|
||||
borderRadius: borderRadius ? borderRadius : 25,
|
||||
resizeMode: 'contain',
|
||||
}}
|
||||
/>
|
||||
) : blurhash ? (
|
||||
<Blurhash
|
||||
blurhash={blurhash!}
|
||||
style={{
|
||||
height: height ?? width,
|
||||
width: width,
|
||||
borderRadius: borderRadius ? borderRadius : 25,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Square
|
||||
backgroundColor='$amethyst'
|
||||
width={width}
|
||||
height={height ?? width}
|
||||
borderRadius={borderRadius ? borderRadius : 25}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import Client from '../../../api/client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { Image } from 'expo-image'
|
||||
import { getToken, getTokenValue, SizeTokens } from 'tamagui'
|
||||
|
||||
interface ImageProps {
|
||||
item: BaseItemDto
|
||||
circular?: boolean | undefined
|
||||
width?: SizeTokens | undefined
|
||||
height?: SizeTokens | undefined
|
||||
}
|
||||
|
||||
export default function ItemImage({
|
||||
item,
|
||||
circular,
|
||||
width,
|
||||
height,
|
||||
}: ImageProps): React.JSX.Element {
|
||||
return (
|
||||
<Image
|
||||
source={getImageApi(Client.api!).getItemImageUrlById(item.Id!)}
|
||||
style={{
|
||||
borderRadius: circular
|
||||
? width
|
||||
? width
|
||||
: getTokenValue('$12') + getToken('$5')
|
||||
: getTokenValue('$2'),
|
||||
width: width ? width : getToken('$12') + getToken('$5'),
|
||||
height: height ? height : getToken('$12') + getToken('$5'),
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ToastViewport } from '@tamagui/toast'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
|
||||
export default function SafeToastViewport(): React.JSX.Element {
|
||||
const { left, top, right } = useSafeAreaInsets()
|
||||
return <ToastViewport flexDirection='column-reverse' top={top} left={left} right={right} />
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Toast as TamaguiToast, useToastState } from '@tamagui/toast'
|
||||
import { YStack } from 'tamagui'
|
||||
|
||||
export default function Toast(): React.JSX.Element | null {
|
||||
const currentToast = useToastState()
|
||||
|
||||
if (!currentToast || currentToast.isHandledNatively) return null
|
||||
return (
|
||||
<TamaguiToast
|
||||
key={currentToast.id}
|
||||
duration={currentToast.duration}
|
||||
enterStyle={{ opacity: 0, scale: 0.5, y: -25 }}
|
||||
exitStyle={{ opacity: 0, scale: 1, y: -20 }}
|
||||
y={0}
|
||||
opacity={1}
|
||||
scale={1}
|
||||
animation='200ms'
|
||||
viewportName={currentToast.viewportName}
|
||||
>
|
||||
<YStack>
|
||||
<TamaguiToast.Title>{currentToast.title}</TamaguiToast.Title>
|
||||
{!!currentToast.message && (
|
||||
<TamaguiToast.Description>{currentToast.message}</TamaguiToast.Description>
|
||||
)}
|
||||
</YStack>
|
||||
</TamaguiToast>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { ItemCard } from '../../Global/components/item-card'
|
||||
import { H2 } from '../../../components/Global/helpers/text'
|
||||
import { StackParamList } from '../../../components/types'
|
||||
import React from 'react'
|
||||
import { FlatList } from 'react-native'
|
||||
import { View, XStack } from 'tamagui'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchUserPlaylists } from '../../../api/queries/functions/playlists'
|
||||
|
||||
export default function Playlists({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { data: playlists } = useQuery({
|
||||
queryKey: [QueryKeys.UserPlaylists],
|
||||
queryFn: () => fetchUserPlaylists(),
|
||||
})
|
||||
|
||||
return (
|
||||
<View>
|
||||
<XStack alignContent='center' marginHorizontal={'$2'}>
|
||||
<H2 textAlign='left'>Your Playlists</H2>
|
||||
</XStack>
|
||||
<FlatList
|
||||
horizontal
|
||||
data={playlists}
|
||||
renderItem={({ item: playlist }) => (
|
||||
<ItemCard
|
||||
item={playlist}
|
||||
size={'$11'}
|
||||
squared
|
||||
caption={playlist.Name ?? 'Untitled Playlist'}
|
||||
onPress={() => {
|
||||
navigation.navigate('Playlist', {
|
||||
playlist,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
5
index.js
5
index.js
@@ -2,15 +2,14 @@ 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 { PlaybackService } from './src/player/service'
|
||||
import TrackPlayer from 'react-native-track-player'
|
||||
import Client from './api/client'
|
||||
import Client from './src/api/client'
|
||||
|
||||
// Initialize API client instance
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
Client.instance
|
||||
|
||||
Client.instance
|
||||
console.debug('Created Jellify client')
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App)
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
#import "RNCarPlay.h"
|
||||
#import <Expo/Expo.h>
|
||||
#import <RCTAppDelegate.h>
|
||||
#import <RCTAppDelegate.h>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
00E356F31AD99517003FC87E /* JellifyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* JellifyTests.m */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
217EBE16A3E8C5FBF476C905 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F757EB73303E0AC21EF34F64 /* PrivacyInfo.xcprivacy */; };
|
||||
66BC9C5D1B536CD0799EEC89 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82DE980BB8253E3C5F2207CE /* ExpoModulesProvider.swift */; };
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||
92C580068317633958E4B0F9 /* libPods-Jellify.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4912CEDF8E675A9B3515C88E /* libPods-Jellify.a */; };
|
||||
CF620D0C2CF2BB210045E433 /* Aileron-Italic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CF620CFC2CF2BB1F0045E433 /* Aileron-Italic.otf */; };
|
||||
@@ -82,7 +81,6 @@
|
||||
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = Jellify/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
4912CEDF8E675A9B3515C88E /* libPods-Jellify.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Jellify.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Jellify/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
82DE980BB8253E3C5F2207CE /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Jellify/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
A4403789D3D6FBE6706E62B4 /* Pods-Jellify.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Jellify.release.xcconfig"; path = "Target Support Files/Pods-Jellify/Pods-Jellify.release.xcconfig"; sourceTree = "<group>"; };
|
||||
ACD0D4797EFB0AA1C5B6FC7D /* Pods-Jellify.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Jellify.debug.xcconfig"; path = "Target Support Files/Pods-Jellify/Pods-Jellify.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
CF620CFC2CF2BB1F0045E433 /* Aileron-Italic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Aileron-Italic.otf"; path = "../assets/fonts/Aileron-Italic.otf"; sourceTree = "<group>"; };
|
||||
@@ -216,22 +214,6 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
56E1F4734630BBAE3738C238 /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7466A2DFBB2DB18C07F6CEC6 /* Jellify */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7466A2DFBB2DB18C07F6CEC6 /* Jellify */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
82DE980BB8253E3C5F2207CE /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = Jellify;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -265,7 +247,6 @@
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */,
|
||||
56E1F4734630BBAE3738C238 /* ExpoModulesProviders */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@@ -376,7 +357,6 @@
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Jellify" */;
|
||||
buildPhases = (
|
||||
B44EEE05F9243658C0ACC188 /* [CP] Check Pods Manifest.lock */,
|
||||
41245AA25E9CF87045F16E79 /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
@@ -545,25 +525,6 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Jellify/Pods-Jellify-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
41245AA25E9CF87045F16E79 /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[Expo] Configure project";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-Jellify/expo-configure-project.sh\"\n";
|
||||
};
|
||||
B44EEE05F9243658C0ACC188 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -604,7 +565,6 @@
|
||||
CF98CA472D3E99E0003D88B7 /* CarScene.swift in Sources */,
|
||||
CF98CA482D3E99E0003D88B7 /* PhoneScene.swift in Sources */,
|
||||
CF98CA492D3E99E0003D88B7 /* AppDelegate.swift in Sources */,
|
||||
66BC9C5D1B536CD0799EEC89 /* ExpoModulesProvider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -683,7 +643,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development: Jack Caulfield (66Z9J9NX2X)";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development: Jack Caulfield (66Z9J9NX2X)";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 152;
|
||||
CURRENT_PROJECT_VERSION = 158;
|
||||
DEVELOPMENT_TEAM = WAH9CZ8BPG;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -694,7 +654,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.11.15;
|
||||
MARKETING_VERSION = 0.11.21;
|
||||
NEW_SETTING = "";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -725,7 +685,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 152;
|
||||
CURRENT_PROJECT_VERSION = 158;
|
||||
DEVELOPMENT_TEAM = WAH9CZ8BPG;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
@@ -735,7 +695,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 0.11.15;
|
||||
MARKETING_VERSION = 0.11.21;
|
||||
NEW_SETTING = "";
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// ios/AppDelegate.swift
|
||||
import UIKit
|
||||
import CarPlay
|
||||
import React
|
||||
#if DEBUG
|
||||
#if FB_SONARKIT_ENABLED
|
||||
import FlipperKit
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, RCTBridgeDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
var bridge: RCTBridge?;
|
||||
var rootView: RCTRootView?;
|
||||
|
||||
static var shared: AppDelegate { return UIApplication.shared.delegate as! AppDelegate }
|
||||
|
||||
func sourceURL(for bridge: RCTBridge!) -> URL! {
|
||||
#if DEBUG
|
||||
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index");
|
||||
#else
|
||||
return Bundle.main.url(forResource:"main", withExtension:"jsbundle")
|
||||
#endif
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
initializeFlipper(with: application)
|
||||
self.bridge = RCTBridge.init(delegate: self, launchOptions: launchOptions)
|
||||
self.rootView = RCTRootView.init(bridge: self.bridge!, moduleName: "Jellify", initialProperties: nil)
|
||||
super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
if (connectingSceneSession.role == UISceneSession.Role.carTemplateApplication) {
|
||||
let scene = UISceneConfiguration(name: "CarPlay", sessionRole: connectingSceneSession.role)
|
||||
scene.delegateClass = CarSceneDelegate.self
|
||||
return scene
|
||||
} else {
|
||||
let scene = UISceneConfiguration(name: "Phone", sessionRole: connectingSceneSession.role)
|
||||
scene.delegateClass = PhoneSceneDelegate.self
|
||||
return scene
|
||||
}
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
}
|
||||
|
||||
override func bundleURL() -> URL? {
|
||||
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index");
|
||||
}
|
||||
|
||||
private func initializeFlipper(with application: UIApplication) {
|
||||
#if DEBUG
|
||||
#if FB_SONARKIT_ENABLED
|
||||
let client = FlipperClient.shared()
|
||||
let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())
|
||||
client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))
|
||||
client?.add(FKUserDefaultsPlugin(suiteName: nil))
|
||||
client?.add(FlipperKitReactPlugin())
|
||||
client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
|
||||
client?.start()
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// ios/CarScene.swift
|
||||
import Foundation
|
||||
import CarPlay
|
||||
|
||||
class CarSceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
|
||||
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
|
||||
didConnect interfaceController: CPInterfaceController) {
|
||||
RNCarPlay.connect(with: interfaceController, window: templateApplicationScene.carWindow);
|
||||
}
|
||||
|
||||
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnectInterfaceController interfaceController: CPInterfaceController) {
|
||||
RNCarPlay.disconnect()
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,14 @@
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
@@ -17,9 +25,9 @@
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
<string>0A2A.1</string>
|
||||
<string>3B52.1</string>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
@@ -27,16 +35,8 @@
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
<string>E174.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
21
ios/Podfile
21
ios/Podfile
@@ -1,4 +1,3 @@
|
||||
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||
# Resolve react_native_pods.rb with node to allow for hoisting
|
||||
require Pod::Executable.execute_command('node', ['-p',
|
||||
'require.resolve(
|
||||
@@ -6,7 +5,6 @@ require Pod::Executable.execute_command('node', ['-p',
|
||||
{paths: [process.argv[1]]},
|
||||
)', __dir__]).strip
|
||||
|
||||
puts "RCT_NEW_ARCH_ENABLED = " + ENV['RCT_NEW_ARCH_ENABLED'];
|
||||
|
||||
platform :ios, min_ios_version_supported
|
||||
prepare_react_native_project!
|
||||
@@ -18,24 +16,7 @@ if linkage != nil
|
||||
end
|
||||
|
||||
target 'Jellify' do
|
||||
use_expo_modules!
|
||||
|
||||
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||
else
|
||||
config_command = [
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
'ios'
|
||||
]
|
||||
end
|
||||
|
||||
config = use_native_modules!(config_command)
|
||||
config = use_native_modules!
|
||||
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
|
||||
181
ios/Podfile.lock
181
ios/Podfile.lock
@@ -1,78 +1,6 @@
|
||||
PODS:
|
||||
- boost (1.84.0)
|
||||
- Burnt (0.13.0):
|
||||
- ExpoModulesCore
|
||||
- SPAlert (~> 4.2)
|
||||
- SPIndicator (~> 1.6)
|
||||
- DoubleConversion (1.1.6)
|
||||
- EXConstants (17.1.3):
|
||||
- ExpoModulesCore
|
||||
- Expo (53.0.1):
|
||||
- DoubleConversion
|
||||
- ExpoModulesCore
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.11.18.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-hermes
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTAppDelegate
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactAppDependencyProvider
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- ExpoAsset (11.1.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoFileSystem (18.1.7):
|
||||
- ExpoModulesCore
|
||||
- ExpoFont (13.2.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoImage (2.0.7):
|
||||
- ExpoModulesCore
|
||||
- libavif/libdav1d
|
||||
- SDWebImage (~> 5.19.1)
|
||||
- SDWebImageAVIFCoder (~> 0.11.0)
|
||||
- SDWebImageSVGCoder (~> 1.7.0)
|
||||
- ExpoKeepAwake (14.1.3):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (2.3.11):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.11.18.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-hermes
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-jsinspector
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- fast_float (6.1.4)
|
||||
- FBLazyVector (0.79.1)
|
||||
- fmt (11.0.2)
|
||||
@@ -80,11 +8,18 @@ PODS:
|
||||
- hermes-engine (0.79.1):
|
||||
- hermes-engine/Pre-built (= 0.79.1)
|
||||
- hermes-engine/Pre-built (0.79.1)
|
||||
- libavif/core (0.11.1)
|
||||
- libavif/libdav1d (0.11.1):
|
||||
- libavif/core
|
||||
- libdav1d (>= 0.6.0)
|
||||
- libdav1d (1.2.0)
|
||||
- libwebp (1.5.0):
|
||||
- libwebp/demux (= 1.5.0)
|
||||
- libwebp/mux (= 1.5.0)
|
||||
- libwebp/sharpyuv (= 1.5.0)
|
||||
- libwebp/webp (= 1.5.0)
|
||||
- libwebp/demux (1.5.0):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.5.0):
|
||||
- libwebp/demux
|
||||
- libwebp/sharpyuv (1.5.0)
|
||||
- libwebp/webp (1.5.0):
|
||||
- libwebp/sharpyuv
|
||||
- RCT-Folly (2024.11.18.00):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -1411,7 +1346,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-background-actions (4.0.1):
|
||||
- React-Core
|
||||
- react-native-blurhash (2.1.1):
|
||||
- react-native-blur (4.4.1):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1913,6 +1848,10 @@ PODS:
|
||||
- Yoga
|
||||
- RNDeviceInfo (14.0.4):
|
||||
- React-Core
|
||||
- RNFastImage (8.6.3):
|
||||
- React-Core
|
||||
- SDWebImage (~> 5.11.1)
|
||||
- SDWebImageWebPCoder (~> 0.8.4)
|
||||
- RNFS (2.20.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.25.0):
|
||||
@@ -2162,32 +2101,19 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- SDWebImage (5.19.7):
|
||||
- SDWebImage/Core (= 5.19.7)
|
||||
- SDWebImage/Core (5.19.7)
|
||||
- SDWebImageAVIFCoder (0.11.0):
|
||||
- libavif/core (>= 0.11.0)
|
||||
- SDWebImage (~> 5.10)
|
||||
- SDWebImageSVGCoder (1.7.0):
|
||||
- SDWebImage/Core (~> 5.6)
|
||||
- SDWebImage (5.11.1):
|
||||
- SDWebImage/Core (= 5.11.1)
|
||||
- SDWebImage/Core (5.11.1)
|
||||
- SDWebImageWebPCoder (0.8.5):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.10)
|
||||
- SocketRocket (0.7.1)
|
||||
- SPAlert (4.2.0)
|
||||
- SPIndicator (1.6.4)
|
||||
- SwiftAudioEx (1.1.0)
|
||||
- Yoga (0.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
||||
- Burnt (from `../node_modules/burnt/ios`)
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- EXConstants (from `../node_modules/expo-constants/ios`)
|
||||
- Expo (from `../node_modules/expo`)
|
||||
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
||||
- ExpoFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||
- ExpoFont (from `../node_modules/expo-font/ios`)
|
||||
- ExpoImage (from `../node_modules/expo-image/ios`)
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
|
||||
- fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
|
||||
@@ -2227,7 +2153,7 @@ DEPENDENCIES:
|
||||
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
|
||||
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
|
||||
- react-native-background-actions (from `../node_modules/react-native-background-actions`)
|
||||
- react-native-blurhash (from `../node_modules/react-native-blurhash`)
|
||||
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
|
||||
- react-native-carplay (from `../node_modules/react-native-carplay`)
|
||||
- react-native-mmkv (from `../node_modules/react-native-mmkv`)
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
@@ -2267,6 +2193,7 @@ DEPENDENCIES:
|
||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
|
||||
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
|
||||
- RNFastImage (from `../node_modules/react-native-fast-image`)
|
||||
- RNFS (from `../node_modules/react-native-fs`)
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
@@ -2277,39 +2204,17 @@ DEPENDENCIES:
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- libavif
|
||||
- libdav1d
|
||||
- libwebp
|
||||
- SDWebImage
|
||||
- SDWebImageAVIFCoder
|
||||
- SDWebImageSVGCoder
|
||||
- SDWebImageWebPCoder
|
||||
- SocketRocket
|
||||
- SPAlert
|
||||
- SPIndicator
|
||||
- SwiftAudioEx
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
boost:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec"
|
||||
Burnt:
|
||||
:path: "../node_modules/burnt/ios"
|
||||
DoubleConversion:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
EXConstants:
|
||||
:path: "../node_modules/expo-constants/ios"
|
||||
Expo:
|
||||
:path: "../node_modules/expo"
|
||||
ExpoAsset:
|
||||
:path: "../node_modules/expo-asset/ios"
|
||||
ExpoFileSystem:
|
||||
:path: "../node_modules/expo-file-system/ios"
|
||||
ExpoFont:
|
||||
:path: "../node_modules/expo-font/ios"
|
||||
ExpoImage:
|
||||
:path: "../node_modules/expo-image/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
ExpoModulesCore:
|
||||
:path: "../node_modules/expo-modules-core"
|
||||
fast_float:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/fast_float.podspec"
|
||||
FBLazyVector:
|
||||
@@ -2385,8 +2290,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
|
||||
react-native-background-actions:
|
||||
:path: "../node_modules/react-native-background-actions"
|
||||
react-native-blurhash:
|
||||
:path: "../node_modules/react-native-blurhash"
|
||||
react-native-blur:
|
||||
:path: "../node_modules/@react-native-community/blur"
|
||||
react-native-carplay:
|
||||
:path: "../node_modules/react-native-carplay"
|
||||
react-native-mmkv:
|
||||
@@ -2465,6 +2370,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/@react-native-masked-view/masked-view"
|
||||
RNDeviceInfo:
|
||||
:path: "../node_modules/react-native-device-info"
|
||||
RNFastImage:
|
||||
:path: "../node_modules/react-native-fast-image"
|
||||
RNFS:
|
||||
:path: "../node_modules/react-native-fs"
|
||||
RNGestureHandler:
|
||||
@@ -2482,23 +2389,13 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
|
||||
Burnt: 0616363a206ed96ba9af3e2eb0e184dc97be1458
|
||||
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
|
||||
EXConstants: 01a258d03504c87ef015dfd0fe8d92a7fc1d8345
|
||||
Expo: 6dd724d3322082adfa13b092b3729709cb590155
|
||||
ExpoAsset: c97af8e75cd5165aa0c48266eb1bb8d2a57e5caa
|
||||
ExpoFileSystem: 175267faf2b38511b01ac110243b13754dac57d3
|
||||
ExpoFont: ab96f88d5e959232152c8dcecafd8ba1235c5763
|
||||
ExpoImage: d840b256050f4428d2942bc2b6e9251f9e0d7021
|
||||
ExpoKeepAwake: 213acedecafb6fda8c0ffedad22ee9e2903400c5
|
||||
ExpoModulesCore: f98b254540e59e7eabc9628ba636789959295ead
|
||||
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
|
||||
FBLazyVector: abbac80c6f89e71a8c55c7e92ec015c8a9496753
|
||||
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
|
||||
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
|
||||
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
|
||||
hermes-engine: c32f2e405098bc1ebe30630a051ddce6f21d3c2e
|
||||
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
|
||||
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
|
||||
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
|
||||
RCTDeprecation: 0ada4fb1e5c5637bff940dc40b94e2d3bf96b0ab
|
||||
RCTRequired: 76ca80ff10acb3834ed0dacba9645645009578a2
|
||||
@@ -2531,7 +2428,7 @@ SPEC CHECKSUMS:
|
||||
React-Mapbuffer: 8df5296f9d9a61f980d293b55026cfebcd8dfb0a
|
||||
React-microtasksnativemodule: c8ed30f8ec30affbc73411c54207bd67b1125bbb
|
||||
react-native-background-actions: 48e6bad9e2a47e3b04858634c5a05ea11062f680
|
||||
react-native-blurhash: 773f3726f5932f05917a3f4828648f155f087c7d
|
||||
react-native-blur: 06d0f9906ecd6cde3a42de16c6cd829a2bf0710c
|
||||
react-native-carplay: 8f388f6f73e5e0f73ed154ad8794371343ee20c0
|
||||
react-native-mmkv: d3cc73d2554fafa20dc5b86386359034d1faf8ff
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
@@ -2571,21 +2468,19 @@ SPEC CHECKSUMS:
|
||||
ReactCommon: aa48e4fddbc6a0afa19dca39a1b6016c150b5db4
|
||||
RNCMaskedView: ae521efb1c6c2b183ae0f8479487db03c826184c
|
||||
RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047
|
||||
RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87
|
||||
RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8
|
||||
RNGestureHandler: ebef699ea17e7c0006c1074e1e423ead60ce0121
|
||||
RNReactNativeHapticFeedback: 851adf794e1fcdc0664d80820fa3272ee8a6a538
|
||||
RNReanimated: 2313402fe27fecb7237619e9c6fcee3177f08a65
|
||||
RNScreens: 5e0027417985f7b4619410f7fcbd391fa157cc71
|
||||
RNVectorIcons: 941a39b5d3b9d8cf8ac2e2fc09b07bfafbcf9796
|
||||
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
||||
SDWebImageAVIFCoder: 00310d246aab3232ce77f1d8f0076f8c4b021d90
|
||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
||||
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
SPAlert: 735da1f16a887e294719217572ce1f936d8c8782
|
||||
SPIndicator: 93e0a4fb23de51294ac48e874c0f081a5e293e4f
|
||||
SwiftAudioEx: f6aa653770f3a0d3851edaf8d834a30aee4a7646
|
||||
Yoga: d15f5aa644c466e917569ac43b19cbf17975239a
|
||||
|
||||
PODFILE CHECKSUM: 8a165b08a274fa7a2ef8008684d2fa318fd691f2
|
||||
PODFILE CHECKSUM: 6c43107ef8f93930ea4053b68b45c8912648b48c
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// https://docs.swmansion.com/react-native-gesture-handler/docs/guides/testing
|
||||
module.exports = {
|
||||
preset: 'jest-expo',
|
||||
preset: 'react-native',
|
||||
setupFiles: ['./node_modules/react-native-gesture-handler/jestSetup.js'],
|
||||
setupFilesAfterEnv: [
|
||||
'./jest/setup.ts',
|
||||
'./jest/setup-carplay.ts',
|
||||
'./jest/setup-blurhash.ts',
|
||||
'./jest/setup-device-info.js', // JS to prevent Typescript implicit any warning
|
||||
'./jest/setup-reanimated.ts',
|
||||
'./jest/setup-rnfs.ts',
|
||||
'./jest/setup-rntp.ts',
|
||||
'./tamagui.config.ts',
|
||||
'./jest/setup-native-modules.ts',
|
||||
],
|
||||
extensionsToTreatAsEsm: ['.ts', '.tsx'],
|
||||
transformIgnorePatterns: [
|
||||
|
||||
@@ -3,8 +3,8 @@ import React from 'react'
|
||||
import { render } from '@testing-library/react-native'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { QueueProvider } from '../player/queue-provider'
|
||||
import { PlayerProvider } from '../player/player-provider'
|
||||
import { QueueProvider } from '../src/player/queue-provider'
|
||||
import { PlayerProvider } from '../src/player/player-provider'
|
||||
import { View } from 'react-native'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'react-native'
|
||||
import React from 'react'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react-native'
|
||||
import TrackPlayer, { Event } from 'react-native-track-player'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react-native'
|
||||
import { Event } from 'react-native-track-player'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { Button, Text } from 'react-native'
|
||||
|
||||
import { QueueProvider, useQueueContext } from '../player/queue-provider'
|
||||
import { QueueProvider, useQueueContext } from '../src/player/queue-provider'
|
||||
import { eventHandler } from './setup-rntp'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
jest.mock('react-native-blurhash', () => {
|
||||
return {
|
||||
Blurhash: () => null,
|
||||
}
|
||||
})
|
||||
@@ -1,17 +1,29 @@
|
||||
import {
|
||||
ListTemplateConfig,
|
||||
NowPlayingTemplateConfig,
|
||||
TabBarTemplateConfig,
|
||||
} from 'react-native-carplay'
|
||||
|
||||
jest.mock('react-native-carplay', () => {
|
||||
return {
|
||||
ListTemplate: class {
|
||||
constructor(config) {
|
||||
config: ListTemplateConfig
|
||||
|
||||
constructor(config: ListTemplateConfig) {
|
||||
this.config = config
|
||||
}
|
||||
},
|
||||
NowPlayingTemplate: class {
|
||||
constructor(config) {
|
||||
config: NowPlayingTemplateConfig
|
||||
|
||||
constructor(config: NowPlayingTemplateConfig) {
|
||||
this.config = config
|
||||
}
|
||||
},
|
||||
TabBarTemplate: class {
|
||||
constructor(config) {
|
||||
config: TabBarTemplateConfig
|
||||
|
||||
constructor(config: TabBarTemplateConfig) {
|
||||
this.config = config
|
||||
}
|
||||
},
|
||||
|
||||
5
jest/setup-native-modules.ts
Normal file
5
jest/setup-native-modules.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock'
|
||||
|
||||
jest.mock('@react-native-community/netinfo', () => mockRNCNetInfo)
|
||||
@@ -1,4 +1,4 @@
|
||||
jest.mock('../api/client')
|
||||
jest.mock('../src/api/client')
|
||||
|
||||
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter')
|
||||
|
||||
@@ -9,11 +9,3 @@ jest.mock('react-native-haptic-feedback', () => {
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('burnt', () => {
|
||||
return {
|
||||
default: {
|
||||
alert: jest.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { wrapWithReanimatedMetroConfig } = require('react-native-reanimated/metro-config')
|
||||
|
||||
@@ -12,6 +13,6 @@ const config = getDefaultConfig(__dirname, {
|
||||
// https://github.com/expo/expo/issues/23180
|
||||
config.resolver.sourceExts.push('mjs')
|
||||
|
||||
config.watchFolders = ['components', 'api', 'player']
|
||||
config.watchFolders = ['src']
|
||||
|
||||
module.exports = config
|
||||
module.exports = wrapWithReanimatedMetroConfig(config)
|
||||
|
||||
238
package.json
238
package.json
@@ -1,120 +1,120 @@
|
||||
{
|
||||
"name": "jellify",
|
||||
"version": "0.11.15",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init-ios": "echo 'Please run `yarn init-ios:new-arch` to enable the new architecture'",
|
||||
"init-ios:new-arch": "yarn install && yarn run pod:install:new-arch",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=0 bundle exec pod install",
|
||||
"pod:install:new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"pod:clean": "cd ios && pod deintegrate",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
|
||||
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
|
||||
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
|
||||
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
|
||||
"prepare": "husky",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@react-native-community/cli": "^18.0.0",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/material-top-tabs": "^7.2.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@tamagui/config": "^1.126.4",
|
||||
"@tamagui/toast": "^1.126.4",
|
||||
"@tanstack/query-sync-storage-persister": "^5.74.6",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-persist-client": "^5.74.6",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
"axios": "^1.8.4",
|
||||
"bundle": "^2.1.0",
|
||||
"burnt": "^0.13.0",
|
||||
"expo": "^53.0.1",
|
||||
"expo-image": "^2.0.7",
|
||||
"gem": "^2.4.3",
|
||||
"invert-color": "^2.0.0",
|
||||
"jest-expo": "^53.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "19.0.0",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.79.1",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-blurhash": "^2.1.1",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.2",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.25.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "^3.2.0",
|
||||
"react-native-pager-view": "^6.7.1",
|
||||
"react-native-reanimated": "^3.17.5",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "^4.11.0-beta.2",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.14.0",
|
||||
"react-native-track-player": "4.1.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"ruby": "^0.6.1",
|
||||
"tamagui": "^1.126.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native-community/cli-platform-android": "18.0.0",
|
||||
"@react-native-community/cli-platform-ios": "18.0.0",
|
||||
"@react-native/babel-preset": "0.79.1",
|
||||
"@react-native/eslint-config": "0.79.1",
|
||||
"@react-native/metro-config": "0.79.1",
|
||||
"@react-native/typescript-config": "0.79.1",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "19.0.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.6.3",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"lint-staged": "^15.5.0",
|
||||
"patch-package": "8.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react-native-cli-bump-version": "^1.5.1",
|
||||
"react-test-renderer": "19.0.0",
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
"name": "jellify",
|
||||
"version": "0.11.21",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"init-android": "yarn",
|
||||
"init-ios": "yarn init-ios:new-arch",
|
||||
"init-ios:new-arch": "yarn && yarn pod:install:new-arch",
|
||||
"reinstall": "rm -rf ./node_modules && yarn install",
|
||||
"android": "react-native run-android",
|
||||
"ios": "react-native run-ios",
|
||||
"lint": "eslint .",
|
||||
"start": "react-native start",
|
||||
"test": "jest",
|
||||
"tsc": "tsc",
|
||||
"clean:ios": "cd ios && pod deintegrate",
|
||||
"clean:android": "cd android && rm -rf app/ build/",
|
||||
"pod:install": "echo 'Please run `yarn pod:install:new-arch` to enable the new architecture'",
|
||||
"pod:install:new-arch": "cd ios && bundle install && RCT_NEW_ARCH_ENABLED=1 bundle exec pod install",
|
||||
"pod:clean": "cd ios && pod deintegrate",
|
||||
"fastlane:ios:build": "cd ios && bundle exec fastlane build",
|
||||
"fastlane:ios:match": "cd ios && bundle exec fastlane match development",
|
||||
"fastlane:ios:beta": "cd ios && bundle exec fastlane beta",
|
||||
"fastlane:android:build": "cd android && bundle install && bundle exec fastlane build",
|
||||
"androidBuild": "cd android && ./gradlew clean && ./gradlew assembleRelease && cd .. && echo 'find apk in android/app/build/outputs/apk/release'",
|
||||
"prepare": "husky",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/cli": "^18.0.0",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/material-top-tabs": "^7.2.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@tamagui/config": "^1.126.4",
|
||||
"@tanstack/query-sync-storage-persister": "^5.74.6",
|
||||
"@tanstack/react-query": "^5.74.4",
|
||||
"@tanstack/react-query-persist-client": "^5.74.6",
|
||||
"@testing-library/react-native": "^13.2.0",
|
||||
"axios": "^1.8.4",
|
||||
"bundle": "^2.1.0",
|
||||
"gem": "^2.4.3",
|
||||
"invert-color": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "19.0.0",
|
||||
"react-freeze": "^1.0.4",
|
||||
"react-native": "0.79.1",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-carplay": "^2.4.1-beta.0",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.2",
|
||||
"react-native-fast-image": "^8.6.3",
|
||||
"react-native-fs": "^2.20.0",
|
||||
"react-native-gesture-handler": "^2.25.0",
|
||||
"react-native-haptic-feedback": "^2.3.3",
|
||||
"react-native-mmkv": "3.2.0",
|
||||
"react-native-pager-view": "^6.7.1",
|
||||
"react-native-reanimated": "^3.17.5",
|
||||
"react-native-safe-area-context": "^5.4.0",
|
||||
"react-native-screens": "^4.11.0-beta.2",
|
||||
"react-native-swipeable-item": "^2.0.9",
|
||||
"react-native-text-ticker": "^1.14.0",
|
||||
"react-native-toast-message": "^2.3.0",
|
||||
"react-native-track-player": "git+https://github.com/riteshshukla04/react-native-track-player#APM",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-uuid": "^2.0.3",
|
||||
"react-native-vector-icons": "^10.2.0",
|
||||
"ruby": "^0.6.1",
|
||||
"tamagui": "^1.126.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.1",
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@react-native-community/cli-platform-android": "18.0.0",
|
||||
"@react-native-community/cli-platform-ios": "18.0.0",
|
||||
"@react-native/babel-preset": "0.79.1",
|
||||
"@react-native/eslint-config": "0.79.1",
|
||||
"@react-native/metro-config": "0.79.1",
|
||||
"@react-native/typescript-config": "0.79.1",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-native-vector-icons": "^6.4.18",
|
||||
"@types/react-test-renderer": "19.0.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-native": "^5.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.6.3",
|
||||
"jscodeshift": "^0.15.2",
|
||||
"lint-staged": "^15.5.1",
|
||||
"patch-package": "8.0.0",
|
||||
"prettier": "^3.5.3",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-native-cli-bump-version": "^1.5.1",
|
||||
"react-test-renderer": "19.0.0",
|
||||
"typescript": "5.8.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write",
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
export type Queue = BaseItemDto | 'Recently Played' | 'Search' | 'Favorite Tracks' | 'On Repeat'
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Jellyfin } from '@jellyfin/sdk'
|
||||
import { getModel, getUniqueIdSync } from 'react-native-device-info'
|
||||
import { name, version } from '../package.json'
|
||||
import { name, version } from '../../package.json'
|
||||
import { capitalize } from 'lodash'
|
||||
|
||||
console.debug(`Building Jellyfin Info`)
|
||||
@@ -1,4 +1,4 @@
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { isUndefined } from 'lodash'
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseItemDto, MediaType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import { getLibraryApi, getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
export async function addToPlaylist(track: BaseItemDto, playlist: BaseItemDto) {
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../enums/query-keys'
|
||||
import { createApi } from './queries/functions/api'
|
||||
import { createApi } from './queries/api'
|
||||
|
||||
export const useApi = (
|
||||
serverUrl?: string,
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Api } from '@jellyfin/sdk'
|
||||
import { JellyfinInfo } from '../../info'
|
||||
import { JellyfinInfo } from '../info'
|
||||
import _ from 'lodash'
|
||||
|
||||
export function createApi(
|
||||
@@ -1,4 +1,4 @@
|
||||
import Client from '../../client'
|
||||
import Client from '../client'
|
||||
import {
|
||||
BaseItemDto,
|
||||
BaseItemKind,
|
||||
@@ -1,4 +1,4 @@
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import {
|
||||
BaseItemDto,
|
||||
BaseItemKind,
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
SortOrder,
|
||||
} from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { reject } from 'lodash'
|
||||
|
||||
export function fetchFrequentlyPlayed(): Promise<BaseItemDto[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ImageFormat, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import _ from 'lodash'
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
|
||||
export async function fetchItemImage(
|
||||
itemId: string,
|
||||
31
src/api/queries/instant-mixes.ts
Normal file
31
src/api/queries/instant-mixes.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getInstantMixApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import Client from '../client'
|
||||
import { isUndefined } from 'lodash'
|
||||
import QueryConfig from './query.config'
|
||||
|
||||
/**
|
||||
* Fetches an instant mix for a given item
|
||||
* @param item The item to fetch an instant mix for
|
||||
* @returns A promise of a {@link BaseItemDto} array, be it empty or not
|
||||
*/
|
||||
export function fetchInstantMixFromItem(item: BaseItemDto): Promise<BaseItemDto[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isUndefined(Client.api)) return reject(new Error('Client not initialized'))
|
||||
|
||||
getInstantMixApi(Client.api)
|
||||
.getInstantMixFromArtists({
|
||||
itemId: item.Id!,
|
||||
userId: Client.user!.id,
|
||||
limit: QueryConfig.limits.instantMix,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.Items) return resolve(data.Items)
|
||||
return resolve([])
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
return reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
65
src/api/queries/item.ts
Normal file
65
src/api/queries/item.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import Client from '../client'
|
||||
import { BaseItemDto, ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { groupBy, isEmpty, isEqual, isUndefined } from 'lodash'
|
||||
|
||||
export async function fetchItem(itemId: string): Promise<BaseItemDto> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isEmpty(itemId)) reject('No item ID proviced')
|
||||
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
ids: [itemId],
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.data.Items && response.data.TotalRecordCount == 1)
|
||||
resolve(response.data.Items[0])
|
||||
else reject(`${response.data.TotalRecordCount} items returned for ID`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function fetchAlbumDiscs(
|
||||
album: BaseItemDto,
|
||||
): Promise<{ title: string; data: BaseItemDto[] }[]> {
|
||||
return new Promise<{ title: string; data: BaseItemDto[] }[]>((resolve, reject) => {
|
||||
if (isEmpty(album.Id)) reject('No album ID provided')
|
||||
|
||||
if (isUndefined(Client.api)) reject('Client not initialized')
|
||||
|
||||
let sortBy: ItemSortBy[] = []
|
||||
|
||||
sortBy = [ItemSortBy.ParentIndexNumber, ItemSortBy.IndexNumber, ItemSortBy.SortName]
|
||||
|
||||
getItemsApi(Client.api!)
|
||||
.getItems({
|
||||
parentId: album.Id!,
|
||||
sortBy,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
const discs = data.Items
|
||||
? Object.keys(groupBy(data.Items, (track) => track.ParentIndexNumber)).map(
|
||||
(discNumber) => {
|
||||
console.debug(discNumber)
|
||||
return {
|
||||
title: discNumber,
|
||||
data: data.Items!.filter((track: BaseItemDto) =>
|
||||
track.ParentIndexNumber
|
||||
? isEqual(
|
||||
discNumber,
|
||||
(track.ParentIndexNumber ?? 0).toString(),
|
||||
)
|
||||
: track,
|
||||
),
|
||||
}
|
||||
},
|
||||
)
|
||||
: [{ title: '1', data: [] }]
|
||||
|
||||
resolve(discs)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import Client from '../../client'
|
||||
import Client from '../client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getUserViewsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api'
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PlaybackInfoResponse } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import { getAudioApi, getMediaInfoApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
export async function fetchMediaInfo(itemId: string): Promise<PlaybackInfoResponse> {
|
||||
@@ -1,4 +1,4 @@
|
||||
import Client from '../../client'
|
||||
import Client from '../client'
|
||||
import { BaseItemDto, ItemSortBy, SortOrder } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ImageFormat } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
export const QueryConfig = {
|
||||
const QueryConfig = {
|
||||
limits: {
|
||||
recents: 20,
|
||||
recents: 50,
|
||||
instantMix: 50,
|
||||
search: 50, // TODO: make this a paginated search so limits don't even matter
|
||||
similar: 20,
|
||||
},
|
||||
images: {
|
||||
height: 300,
|
||||
@@ -31,3 +33,5 @@ export const QueryConfig = {
|
||||
oneFortnight: 1000 * 60 * 60 * 24 * 7 * 14, // 14 Days
|
||||
},
|
||||
}
|
||||
|
||||
export default QueryConfig
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
SortOrder,
|
||||
} from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api'
|
||||
import { QueryConfig } from '../query.config'
|
||||
import Client from '../../client'
|
||||
import QueryConfig from './query.config'
|
||||
import Client from '../client'
|
||||
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
export async function fetchRecentlyAdded(
|
||||
@@ -28,6 +28,12 @@ export async function fetchRecentlyAdded(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches recently played tracks for a user from the Jellyfin server.
|
||||
* @param limit The number of items to fetch. Defaults to 50
|
||||
* @param offset The offset of the items to fetch.
|
||||
* @returns The recently played items.
|
||||
*/
|
||||
export async function fetchRecentlyPlayed(
|
||||
limit: number = QueryConfig.limits.recents,
|
||||
offset?: number | undefined,
|
||||
@@ -56,6 +62,13 @@ export async function fetchRecentlyPlayed(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches recently played artists for a user from the Jellyfin server,
|
||||
* referencing the recently played tracks.
|
||||
* @param limit The number of items to fetch. Defaults to 50
|
||||
* @param offset The offset of the items to fetch.
|
||||
* @returns The recently played artists.
|
||||
*/
|
||||
export function fetchRecentlyPlayedArtists(
|
||||
limit: number = QueryConfig.limits.recents,
|
||||
offset?: number | undefined,
|
||||
@@ -1,8 +1,8 @@
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { isEmpty, trim } from 'lodash'
|
||||
import { QueryConfig } from '../query.config'
|
||||
import QueryConfig from './query.config'
|
||||
|
||||
/**
|
||||
* Performs a search for items against the Jellyfin server, trimming whitespace
|
||||
@@ -1,10 +1,10 @@
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
|
||||
import QueryConfig from './query.config'
|
||||
export default function fetchSimilar(
|
||||
itemId: string,
|
||||
limit: number = 10,
|
||||
limit: number = QueryConfig.limits.similar,
|
||||
startIndex: number = 0,
|
||||
): Promise<BaseItemDto[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getItemsApi, getSuggestionsApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import Client from '../../../api/client'
|
||||
import Client from '../client'
|
||||
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
|
||||
export async function fetchSearchSuggestions(): Promise<BaseItemDto[]> {
|
||||
183
src/components/Album/index.tsx
Normal file
183
src/components/Album/index.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { HomeAlbumProps, StackParamList } from '../types'
|
||||
import { YStack, XStack, Separator, getToken, Spacer } from 'tamagui'
|
||||
import { H5, Text } from '../Global/helpers/text'
|
||||
import { FlatList, SectionList, useWindowDimensions } from 'react-native'
|
||||
import { RunTimeTicks } from '../Global/helpers/time-codes'
|
||||
import Track from '../Global/components/track'
|
||||
import FavoriteButton from '../Global/components/favorite-button'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import Client from '../../api/client'
|
||||
import { ItemCard } from '../Global/components/item-card'
|
||||
import { fetchAlbumDiscs } from '../../api/queries/item'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import InstantMixButton from '../Global/components/instant-mix-button'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import ItemImage from '../Global/components/image'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* The screen for an Album's track list
|
||||
*
|
||||
* @param route The route object from the parent screen,
|
||||
* containing the {@link BaseItemDto} of the album to display in the params
|
||||
*
|
||||
* @param navigation The navigation object from the parent screen
|
||||
*
|
||||
* @returns A React component
|
||||
*/
|
||||
export function AlbumScreen({ route, navigation }: HomeAlbumProps): React.JSX.Element {
|
||||
const { album } = route.params
|
||||
|
||||
const { data: discs } = useQuery({
|
||||
queryKey: [QueryKeys.ItemTracks, album.Id!],
|
||||
queryFn: () => fetchAlbumDiscs(album),
|
||||
})
|
||||
|
||||
return (
|
||||
<SectionList
|
||||
contentInsetAdjustmentBehavior='automatic'
|
||||
sections={discs ? discs : [{ title: '1', data: [] }]}
|
||||
keyExtractor={(item, index) => item.Id! + index}
|
||||
ItemSeparatorComponent={() => <Separator />}
|
||||
renderSectionHeader={({ section }) => {
|
||||
return discs && discs.length >= 2 ? (
|
||||
<Text
|
||||
paddingVertical={'$2'}
|
||||
paddingLeft={'$4.5'}
|
||||
backgroundColor={'$background'}
|
||||
bold
|
||||
>{`Disc ${section.title}`}</Text>
|
||||
) : null
|
||||
}}
|
||||
ListHeaderComponent={() => AlbumTrackListHeader(album, navigation)}
|
||||
renderItem={({ item: track, index }) => (
|
||||
<Track
|
||||
track={track}
|
||||
tracklist={discs?.flatMap((disc) => disc.data)}
|
||||
index={discs?.flatMap((disc) => disc.data).indexOf(track) ?? index}
|
||||
navigation={navigation}
|
||||
queue={album}
|
||||
/>
|
||||
)}
|
||||
ListFooterComponent={() => AlbumTrackListFooter(album, navigation)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a header for an Album's track list
|
||||
* @param album The {@link BaseItemDto} of the album to render the header for
|
||||
* @param navigation The navigation object from the parent {@link AlbumScreen}
|
||||
* @returns A React component
|
||||
*/
|
||||
function AlbumTrackListHeader(
|
||||
album: BaseItemDto,
|
||||
navigation: NativeStackNavigationProp<StackParamList>,
|
||||
): React.JSX.Element {
|
||||
const { width } = useWindowDimensions()
|
||||
|
||||
return (
|
||||
<YStack marginTop={'$4'} alignItems='center'>
|
||||
<XStack justifyContent='center'>
|
||||
<ItemImage item={album} width={'$20'} height={'$20'} />
|
||||
|
||||
<Spacer />
|
||||
|
||||
<YStack alignContent='center' justifyContent='center'>
|
||||
<H5
|
||||
lineBreakStrategyIOS='standard'
|
||||
textAlign='center'
|
||||
numberOfLines={5}
|
||||
minWidth={width / 2.25}
|
||||
maxWidth={width / 2.25}
|
||||
>
|
||||
{album.Name ?? 'Untitled Album'}
|
||||
</H5>
|
||||
|
||||
<XStack justify='center' marginVertical={'$2'}>
|
||||
<YStack flex={1}>
|
||||
{album.ProductionYear ? (
|
||||
<Text display='block' textAlign='right'>
|
||||
{album.ProductionYear?.toString() ?? 'Unknown Year'}
|
||||
</Text>
|
||||
) : null}
|
||||
</YStack>
|
||||
|
||||
<Separator vertical marginHorizontal={'$3'} />
|
||||
|
||||
<YStack flex={1}>
|
||||
<RunTimeTicks>{album.RunTimeTicks}</RunTimeTicks>
|
||||
</YStack>
|
||||
</XStack>
|
||||
|
||||
<XStack justifyContent='center' marginVertical={'$2'}>
|
||||
<FavoriteButton item={album} />
|
||||
|
||||
<Spacer />
|
||||
|
||||
<InstantMixButton item={album} navigation={navigation} />
|
||||
</XStack>
|
||||
</YStack>
|
||||
</XStack>
|
||||
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
marginTop: getToken('$4'),
|
||||
}}
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
horizontal
|
||||
keyExtractor={(item) => item.Id!}
|
||||
data={album.AlbumArtists}
|
||||
renderItem={({ index, item: artist }) => (
|
||||
<ItemCard
|
||||
size={'$10'}
|
||||
item={artist}
|
||||
caption={artist.Name ?? 'Unknown Artist'}
|
||||
onPress={() => {
|
||||
navigation.navigate('Artist', {
|
||||
artist,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
|
||||
function AlbumTrackListFooter(
|
||||
album: BaseItemDto,
|
||||
navigation: NativeStackNavigationProp<StackParamList>,
|
||||
): React.JSX.Element {
|
||||
return (
|
||||
<YStack marginLeft={'$2'}>
|
||||
{album.ArtistItems && album.ArtistItems.length > 1 && (
|
||||
<>
|
||||
<H5>Featuring</H5>
|
||||
|
||||
<FlatList
|
||||
data={album.ArtistItems}
|
||||
horizontal
|
||||
renderItem={({ item: artist }) => (
|
||||
<ItemCard
|
||||
size={'$8'}
|
||||
item={artist}
|
||||
caption={artist.Name ?? 'Unknown Artist'}
|
||||
onPress={() => {
|
||||
navigation.navigate('Artist', {
|
||||
artist,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</YStack>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { ItemCard } from '../Global/components/item-card'
|
||||
import { FlatList, RefreshControl } from 'react-native'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { fetchFavoriteAlbums } from '../../api/queries/functions/favorites'
|
||||
import { fetchFavoriteAlbums } from '../../api/queries/favorites'
|
||||
|
||||
export default function Albums({ navigation, route }: AlbumsProps): React.JSX.Element {
|
||||
const {
|
||||
@@ -41,7 +41,7 @@ export default function Albums({
|
||||
convertRunTimeTicksToSeconds(album.RunTimeTicks ?? 0) /
|
||||
60 <=
|
||||
30)),
|
||||
)
|
||||
)
|
||||
: []
|
||||
}
|
||||
numColumns={2} // TODO: Make this adjustable
|
||||
@@ -6,7 +6,7 @@ import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import { YStack } from 'tamagui'
|
||||
import Albums from './albums'
|
||||
import SimilarArtists from './similar'
|
||||
import { Image } from 'expo-image'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import {
|
||||
createMaterialTopTabNavigator,
|
||||
MaterialTopTabBar,
|
||||
@@ -37,11 +37,13 @@ export default function ArtistNavigation(): React.JSX.Element {
|
||||
tabBar={(props) => (
|
||||
<>
|
||||
<Animated.View style={[animatedBannerStyle]}>
|
||||
<Image
|
||||
source={getImageApi(Client.api!).getItemImageUrlById(
|
||||
artist.Id!,
|
||||
ImageType.Backdrop,
|
||||
)}
|
||||
<FastImage
|
||||
source={{
|
||||
uri: getImageApi(Client.api!).getItemImageUrlById(
|
||||
artist.Id!,
|
||||
ImageType.Backdrop,
|
||||
),
|
||||
}}
|
||||
style={{ width: width, height: '100%' }}
|
||||
/>
|
||||
</Animated.View>
|
||||
@@ -1,4 +1,4 @@
|
||||
import fetchSimilar from '../../api/queries/functions/similar'
|
||||
import fetchSimilar from '../../api/queries/similar'
|
||||
import Client from '../../api/client'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import {
|
||||
@@ -3,7 +3,7 @@ import { ItemCard } from '../Global/components/item-card'
|
||||
import { ArtistsProps } from '../types'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchFavoriteArtists } from '../../api/queries/functions/favorites'
|
||||
import { fetchFavoriteArtists } from '../../api/queries/favorites'
|
||||
import { YStack } from 'tamagui'
|
||||
import { Text } from '../Global/helpers/text'
|
||||
import { FlatList } from 'react-native'
|
||||
@@ -4,6 +4,8 @@ import { CarPlay, ListTemplate } from 'react-native-carplay'
|
||||
import TrackPlayer from 'react-native-track-player'
|
||||
import uuid from 'react-native-uuid'
|
||||
import CarPlayNowPlaying from './NowPlaying'
|
||||
import { queryClient } from '../../constants/query-client'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
|
||||
const RecentTracksTemplate = (items: BaseItemDto[]) =>
|
||||
new ListTemplate({
|
||||
@@ -20,13 +22,17 @@ const RecentTracksTemplate = (items: BaseItemDto[]) =>
|
||||
},
|
||||
],
|
||||
onItemSelect: async (item) => {
|
||||
await TrackPlayer.setQueue(items.map((item) => mapDtoToTrack(item)))
|
||||
await TrackPlayer.setQueue(
|
||||
items.map((item) =>
|
||||
mapDtoToTrack(item, queryClient.getQueryData([QueryKeys.AudioCache]) ?? []),
|
||||
),
|
||||
)
|
||||
|
||||
await TrackPlayer.skip(item.index)
|
||||
|
||||
await TrackPlayer.play()
|
||||
|
||||
CarPlay.pushTemplate(CarPlayNowPlaying())
|
||||
CarPlay.pushTemplate(CarPlayNowPlaying)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -30,12 +30,9 @@ export default function RecentlyAdded({
|
||||
|
||||
<HorizontalCardList
|
||||
squared
|
||||
data={recentlyAdded?.length ?? 0 > 10 ? recentlyAdded!.slice(0, 10) : recentlyAdded}
|
||||
onSeeMore={() => {
|
||||
navigation.navigate('Albums', {
|
||||
albums: recentlyAdded,
|
||||
})
|
||||
}}
|
||||
data={
|
||||
(recentlyAdded?.length ?? 0 > 10) ? recentlyAdded!.slice(0, 10) : recentlyAdded
|
||||
}
|
||||
renderItem={({ item }) => (
|
||||
<ItemCard
|
||||
caption={item.Name}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchRecentlyAdded } from '../../api/queries/functions/recents'
|
||||
import { fetchRecentlyAdded, fetchRecentlyPlayed } from '../../api/queries/recents'
|
||||
import { QueryKeys } from '../../enums/query-keys'
|
||||
import { createContext, ReactNode, useContext, useState } from 'react'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
@@ -8,6 +8,7 @@ interface DiscoverContext {
|
||||
refreshing: boolean
|
||||
refresh: () => void
|
||||
recentlyAdded: BaseItemDto[] | undefined
|
||||
recentlyPlayed: BaseItemDto[] | undefined
|
||||
}
|
||||
|
||||
const DiscoverContextInitializer = () => {
|
||||
@@ -16,13 +17,17 @@ const DiscoverContextInitializer = () => {
|
||||
const { data: recentlyAdded, refetch } = useQuery({
|
||||
queryKey: [QueryKeys.RecentlyAdded],
|
||||
queryFn: () => fetchRecentlyAdded(),
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
})
|
||||
|
||||
const { data: recentlyPlayed, refetch: refetchRecentlyPlayed } = useQuery({
|
||||
queryKey: [QueryKeys.RecentlyPlayed],
|
||||
queryFn: () => fetchRecentlyPlayed(),
|
||||
})
|
||||
|
||||
const refresh = async () => {
|
||||
setRefreshing(true)
|
||||
|
||||
await Promise.all([refetch()])
|
||||
await Promise.all([refetch(), refetchRecentlyPlayed()])
|
||||
setRefreshing(false)
|
||||
}
|
||||
|
||||
@@ -30,6 +35,7 @@ const DiscoverContextInitializer = () => {
|
||||
refreshing,
|
||||
refresh,
|
||||
recentlyAdded,
|
||||
recentlyPlayed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +43,7 @@ const DiscoverContext = createContext<DiscoverContext>({
|
||||
refreshing: false,
|
||||
refresh: () => {},
|
||||
recentlyAdded: undefined,
|
||||
recentlyPlayed: undefined,
|
||||
})
|
||||
|
||||
export const DiscoverProvider: ({ children }: { children: ReactNode }) => React.JSX.Element = ({
|
||||
@@ -44,19 +51,9 @@ export const DiscoverProvider: ({ children }: { children: ReactNode }) => React.
|
||||
}: {
|
||||
children: ReactNode
|
||||
}) => {
|
||||
const { refreshing, refresh, recentlyAdded } = DiscoverContextInitializer()
|
||||
const context = DiscoverContextInitializer()
|
||||
|
||||
return (
|
||||
<DiscoverContext.Provider
|
||||
value={{
|
||||
refreshing,
|
||||
refresh,
|
||||
recentlyAdded,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DiscoverContext.Provider>
|
||||
)
|
||||
return <DiscoverContext.Provider value={context}>{children}</DiscoverContext.Provider>
|
||||
}
|
||||
|
||||
export const useDiscoverContext = () => useContext(DiscoverContext)
|
||||
@@ -7,6 +7,7 @@ import Albums from '../Albums/component'
|
||||
import { AlbumScreen } from '../Album'
|
||||
import { ArtistScreen } from '../Artist'
|
||||
import { DiscoverProvider } from './provider'
|
||||
import InstantMix from '../InstantMix/component'
|
||||
|
||||
export const DiscoverStack = createNativeStackNavigator<StackParamList>()
|
||||
|
||||
@@ -47,6 +48,16 @@ export function Discover(): React.JSX.Element {
|
||||
|
||||
<DiscoverStack.Screen name='Albums' component={Albums} />
|
||||
|
||||
<DiscoverStack.Screen
|
||||
name='InstantMix'
|
||||
component={InstantMix}
|
||||
options={({ route }) => ({
|
||||
title: route.params.item.Name
|
||||
? `${route.params.item.Name} Mix`
|
||||
: 'Instant Mix',
|
||||
})}
|
||||
/>
|
||||
|
||||
<DiscoverStack.Group screenOptions={{ presentation: 'modal' }}>
|
||||
<DiscoverStack.Screen
|
||||
name='Details'
|
||||
@@ -4,7 +4,7 @@ import { Text } from '../helpers/text'
|
||||
import { BaseItemDto, ImageType } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchItemImage } from '../../../api/queries/functions/images'
|
||||
import { fetchItemImage } from '../../../api/queries/images'
|
||||
|
||||
interface AvatarProps extends TamaguiAvatarProps {
|
||||
item: BaseItemDto
|
||||
@@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { getTokens, Spinner } from 'tamagui'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchUserData } from '../../../api/queries/functions/favorites'
|
||||
import { fetchUserData } from '../../../api/queries/favorites'
|
||||
import { useJellifyUserDataContext } from '../../../components/user-data-provider'
|
||||
|
||||
interface SetFavoriteMutation {
|
||||
@@ -57,6 +57,6 @@ export function isFavoriteItem(item: BaseItemDto): boolean {
|
||||
return isUndefined(item.UserData)
|
||||
? false
|
||||
: isUndefined(item.UserData.IsFavorite)
|
||||
? false
|
||||
: item.UserData.IsFavorite
|
||||
? false
|
||||
: item.UserData.IsFavorite
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { getToken, Spacer, YStack } from 'tamagui'
|
||||
import Icon from '../helpers/icon'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchUserData } from '../../../api/queries/functions/favorites'
|
||||
import { fetchUserData } from '../../../api/queries/favorites'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export default function FavoriteIcon({ item }: { item: BaseItemDto }): React.JSX.Element {
|
||||
58
src/components/Global/components/image.tsx
Normal file
58
src/components/Global/components/image.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import Client from '../../../api/client'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { StyleProp } from 'react-native'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
import { FontSizeTokens, getFontSizeToken, getToken, getTokenValue, Token } from 'tamagui'
|
||||
|
||||
interface ImageProps {
|
||||
item: BaseItemDto
|
||||
circular?: boolean | undefined
|
||||
width?: Token | undefined
|
||||
height?: Token | undefined
|
||||
style?: ImageStyle | undefined
|
||||
}
|
||||
|
||||
export default function ItemImage({
|
||||
item,
|
||||
circular,
|
||||
width,
|
||||
height,
|
||||
style,
|
||||
}: ImageProps): React.JSX.Element {
|
||||
return (
|
||||
<FastImage
|
||||
source={{ uri: getImageApi(Client.api!).getItemImageUrlById(item.Id!) }}
|
||||
style={{
|
||||
borderRadius: getBorderRadius(circular, width),
|
||||
width: !isUndefined(width)
|
||||
? getTokenValue(width)
|
||||
: getToken('$12') + getToken('$5'),
|
||||
height: !isUndefined(height)
|
||||
? getTokenValue(height)
|
||||
: getToken('$12') + getToken('$5'),
|
||||
alignSelf: 'center',
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the border radius for the image
|
||||
* @param circular - Whether the image is circular
|
||||
* @param width - The width of the image
|
||||
* @returns The border radius of the image
|
||||
*/
|
||||
function getBorderRadius(circular: boolean | undefined, width: Token | undefined): number {
|
||||
let borderRadius
|
||||
|
||||
if (circular) {
|
||||
borderRadius = width ? getTokenValue(width) : getTokenValue('$12') + getToken('$5')
|
||||
} else if (!isUndefined(width)) {
|
||||
borderRadius = getTokenValue(width) / 10
|
||||
}
|
||||
|
||||
return borderRadius
|
||||
}
|
||||
39
src/components/Global/components/instant-mix-button.tsx
Normal file
39
src/components/Global/components/instant-mix-button.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import React from 'react'
|
||||
import { StackParamList } from '../../types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { fetchInstantMixFromItem } from '../../../api/queries/instant-mixes'
|
||||
import Icon from '../helpers/icon'
|
||||
import { getToken, Spacer, Spinner } from 'tamagui'
|
||||
import { useColorScheme } from 'react-native'
|
||||
|
||||
export default function InstantMixButton({
|
||||
item,
|
||||
navigation,
|
||||
}: {
|
||||
item: BaseItemDto
|
||||
navigation: NativeStackNavigationProp<StackParamList>
|
||||
}): React.JSX.Element {
|
||||
const { data, isFetching, refetch } = useQuery({
|
||||
queryKey: [QueryKeys.InstantMix, item.Id!],
|
||||
queryFn: () => fetchInstantMixFromItem(item),
|
||||
})
|
||||
|
||||
const isDarkMode = useColorScheme() === 'dark'
|
||||
return data ? (
|
||||
<Icon
|
||||
name='compass-outline'
|
||||
color={isDarkMode ? getToken('$color.success') : getToken('$color.grape')}
|
||||
onPress={() =>
|
||||
navigation.navigate('InstantMix', {
|
||||
item,
|
||||
mix: data,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Spacer />
|
||||
)
|
||||
}
|
||||
@@ -3,12 +3,12 @@ import type { CardProps as TamaguiCardProps } from 'tamagui'
|
||||
import { getToken, Card as TamaguiCard, View, YStack } from 'tamagui'
|
||||
import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models'
|
||||
import { Text } from '../helpers/text'
|
||||
import { Image } from 'expo-image'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import Client from '../../../api/client'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchMediaInfo } from '../../../api/queries/functions/media'
|
||||
import { fetchMediaInfo } from '../../../api/queries/media'
|
||||
|
||||
interface CardProps extends TamaguiCardProps {
|
||||
caption?: string | null | undefined
|
||||
@@ -50,15 +50,12 @@ export function ItemCard(props: CardProps) {
|
||||
)} */}
|
||||
</TamaguiCard.Footer>
|
||||
<TamaguiCard.Background>
|
||||
<Image
|
||||
source={getImageApi(Client.api!).getItemImageUrlById(
|
||||
props.item.Type === 'Audio' ? props.item.AlbumId! : props.item.Id!,
|
||||
)}
|
||||
placeholder={
|
||||
props.item.ImageBlurHashes && props.item.ImageBlurHashes['Primary']
|
||||
? props.item.ImageBlurHashes['Primary'][0]
|
||||
: undefined
|
||||
}
|
||||
<FastImage
|
||||
source={{
|
||||
uri: getImageApi(Client.api!).getItemImageUrlById(
|
||||
props.item.Type === 'Audio' ? props.item.AlbumId! : props.item.Id!,
|
||||
),
|
||||
}}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
@@ -4,12 +4,12 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { getTokens, Separator, Spacer, View, XStack, YStack } from 'tamagui'
|
||||
import { Text } from '../helpers/text'
|
||||
import { useSafeAreaFrame } from 'react-native-safe-area-context'
|
||||
import BlurhashedImage from './blurhashed-image'
|
||||
import Icon from '../helpers/icon'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { RunTimeTicks } from '../helpers/time-codes'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
import { usePlayerContext } from '../../../player/player-provider'
|
||||
import ItemImage from './image'
|
||||
|
||||
export default function Item({
|
||||
item,
|
||||
@@ -75,11 +75,9 @@ export default function Item({
|
||||
paddingVertical={'$2'}
|
||||
marginHorizontal={'$1'}
|
||||
>
|
||||
<BlurhashedImage
|
||||
item={item}
|
||||
width={width / 9}
|
||||
borderRadius={item.Type === 'MusicArtist' ? width / 9 : 2}
|
||||
/>
|
||||
<YStack flex={1}>
|
||||
<ItemImage item={item} height={'$12'} width={'$12'} />
|
||||
</YStack>
|
||||
|
||||
<YStack
|
||||
marginLeft={'$1'}
|
||||
@@ -97,7 +95,7 @@ export default function Item({
|
||||
)}
|
||||
</YStack>
|
||||
|
||||
<XStack justifyContent='space-between' alignItems='center' flex={1}>
|
||||
<XStack justifyContent='space-between' alignItems='center' flex={2}>
|
||||
{item.UserData?.IsFavorite ? (
|
||||
<Icon small color={getTokens().color.telemagenta.val} name='heart' />
|
||||
) : (
|
||||
@@ -10,14 +10,14 @@ import { StackParamList } from '../../../components/types'
|
||||
import { QueuingType } from '../../../enums/queuing-type'
|
||||
import { Queue } from '../../../player/types/queue-item'
|
||||
import FavoriteIcon from './favorite-icon'
|
||||
import { Image } from 'expo-image'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { getImageApi } from '@jellyfin/sdk/lib/utils/api'
|
||||
import Client from '../../../api/client'
|
||||
import { networkStatusTypes } from '../../../components/Network/internetConnectionWatcher'
|
||||
import { useNetworkContext } from '../../../components/Network/provider'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { QueryKeys } from '../../../enums/query-keys'
|
||||
import { fetchMediaInfo } from '../../../api/queries/functions/media'
|
||||
import { fetchMediaInfo } from '../../../api/queries/media'
|
||||
import { useQueueContext } from '../../../player/queue-provider'
|
||||
|
||||
interface TrackProps {
|
||||
@@ -34,6 +34,8 @@ interface TrackProps {
|
||||
prependElement?: React.JSX.Element | undefined
|
||||
showRemove?: boolean | undefined
|
||||
onRemove?: () => void | undefined
|
||||
dragHandle?: (() => void) | undefined // Optional drag handle trigger
|
||||
showDragHandle?: boolean | undefined // Show drag handle
|
||||
}
|
||||
|
||||
export default function Track({
|
||||
@@ -50,6 +52,8 @@ export default function Track({
|
||||
prependElement,
|
||||
showRemove,
|
||||
onRemove,
|
||||
dragHandle,
|
||||
showDragHandle,
|
||||
}: TrackProps): React.JSX.Element {
|
||||
const theme = useTheme()
|
||||
const { nowPlaying, useStartPlayback } = usePlayerContext()
|
||||
@@ -100,10 +104,23 @@ export default function Track({
|
||||
item: track,
|
||||
isNested: isNested,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
paddingVertical={'$2'}
|
||||
>
|
||||
{/* Drag handle, only if enabled */}
|
||||
{showDragHandle && dragHandle && (
|
||||
<YStack alignContent='center' justifyContent='center' marginRight={'$2'}>
|
||||
<Icon
|
||||
name='drag-vertical'
|
||||
size={22}
|
||||
color={getTokens().color.amethyst}
|
||||
onPress={dragHandle}
|
||||
accessibilityLabel='Reorder'
|
||||
/>
|
||||
</YStack>
|
||||
)}
|
||||
|
||||
{prependElement && (
|
||||
<YStack alignContent='center' justifyContent='center' flex={1}>
|
||||
{prependElement}
|
||||
@@ -118,8 +135,10 @@ export default function Track({
|
||||
minHeight={showArtwork ? '$4' : 'unset'}
|
||||
>
|
||||
{showArtwork ? (
|
||||
<Image
|
||||
source={getImageApi(Client.api!).getItemImageUrlById(track.AlbumId!)}
|
||||
<FastImage
|
||||
source={{
|
||||
uri: getImageApi(Client.api!).getItemImageUrlById(track.AlbumId!),
|
||||
}}
|
||||
style={{
|
||||
width: getToken('$12'),
|
||||
height: getToken('$12'),
|
||||
@@ -140,10 +159,10 @@ export default function Track({
|
||||
isPlaying
|
||||
? getTokens().color.telemagenta
|
||||
: isOffline
|
||||
? isDownloaded
|
||||
? theme.color
|
||||
: '$purpleGray'
|
||||
: theme.color
|
||||
? isDownloaded
|
||||
? theme.color
|
||||
: '$purpleGray'
|
||||
: theme.color
|
||||
}
|
||||
lineBreakStrategyIOS='standard'
|
||||
numberOfLines={1}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user