Compare commits

...

67 Commits

Author SHA1 Message Date
Lei Nelissen
3eab7793f5 Allow for HTTP connections in Android Webviews 2021-02-07 23:26:45 +01:00
Lei Nelissen
66eb4bd61d Add environment variables for sentry 2021-02-07 23:19:49 +01:00
Lei Nelissen
3312e99911 Minor package versions upgrade 2021-02-07 23:06:34 +01:00
Lei Nelissen
3ae967e137 Base setup for Sentry 2021-01-29 00:47:17 +01:00
Lei Nelissen
d18daf027e Update packages 2021-01-28 17:40:11 +01:00
Lei Nelissen
0ae7e3ba0c Release new build 2020-12-02 20:51:03 +01:00
Lei Nelissen
e918f089e2 Add Spanish locale as getter 2020-12-02 20:35:46 +01:00
Lei Nelissen
491f019fb3 Merge pull request #30 from raikoon/add-es-locale
Add Spanish locale
2020-12-02 20:24:43 +01:00
unknown
0af730dabe Add Spanish locale 2020-11-25 14:16:26 +01:00
Lei Nelissen
fadfabc868 Merge pull request #29 from leinelissen/translations_src-localisation-lang-en-locale-json--master_fr
Translate '/src/localisation/lang/en/locale.json' in 'fr'
2020-11-11 12:49:54 +01:00
transifex-integration[bot]
de73fe6358 Apply translations in fr
translation completed updated for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.
2020-11-11 11:46:49 +00:00
Lei Nelissen
6f3ed9c988 Release build number 6 2020-11-04 17:05:06 +01:00
Lei Nelissen
ff484cebed Add nl locale as option 2020-11-03 23:10:21 +01:00
Lei Nelissen
1e14ed3271 Merge pull request #23 from leinelissen/translations_src-localisation-lang-en-locale-json--master_nl
Translate '/src/localisation/lang/en/locale.json' in 'nl'
2020-11-03 23:09:40 +01:00
Lei Nelissen
954fe38531 Merge pull request #24 from leinelissen/translations_src-localisation-lang-en-locale-json--master_fr
Translate '/src/localisation/lang/en/locale.json' in 'fr'
2020-11-03 23:09:13 +01:00
transifex-integration[bot]
83cf0e4d56 Apply translations in fr
translation completed for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.
2020-11-03 18:55:06 +00:00
transifex-integration[bot]
3b98eb4bf1 Apply translations in nl
translation completed updated for the source file '/src/localisation/lang/en/locale.json'
on the 'nl' language.
2020-11-03 16:55:52 +00:00
Lei Nelissen
49bb4dfaab Merge branch 'master' of https://github.com/leinelissen/jellyfin-audio-player 2020-11-03 10:21:32 +01:00
Lei Nelissen
9f9e61017e Update project version for release 2020-11-03 10:21:17 +01:00
Lei Nelissen
65c7db9911 Merge pull request #21 from leinelissen/translations_src-localisation-lang-en-locale-json--master_fr
Translate '/src/localisation/lang/en/locale.json' in 'fr' [manual sync]
2020-11-02 23:35:15 +01:00
Lei Nelissen
481a411e4c Delete locale.json 2020-11-02 23:34:19 +01:00
transifex-integration[bot]
8eeddebad7 Apply translations in fr
at least 50% translated updated for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-11-02 22:33:08 +00:00
transifex-integration[bot]
974c73e70a Apply translations in fr
at least 50% translated updated for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-11-02 22:28:20 +00:00
Lei Nelissen
0988e10a09 Add example locale? 2020-11-02 23:20:26 +01:00
Lei Nelissen
9ea0753912 Give locale file static name so Transifex is better at recognizing it 2020-11-02 23:06:24 +01:00
Lei Nelissen
6cf421f24f Implement locales in codebase 2020-11-02 22:50:00 +01:00
Lei Nelissen
0df9d4a621 Add localisation files 2020-11-02 22:43:11 +01:00
Lei Nelissen
6a1d75c27c Update packages for iOS 14 2020-11-02 22:42:57 +01:00
Lei Nelissen
c88158cf16 (1) Play whole album when selecting a single track
(2) Create popup window on track long-press in which the track can be added to the end or front of the queue
(3) Add Redux counter for added tracks so that the queue is properly updated
2020-08-28 16:28:49 +02:00
Lei Nelissen
3026fdf4da Add repeat mode 2020-08-28 14:17:54 +02:00
Lei Nelissen
12b53eca4a Allow for clearing the Redux store from the settings page 2020-08-28 14:17:37 +02:00
Lei Nelissen
3ff971a773 Support FLAC 2020-08-28 12:45:21 +02:00
Lei Nelissen
8fac21f4d8 Add button for clearing current queue 2020-08-28 12:45:00 +02:00
Lei Nelissen
6e85013b5a (1) Fix missing image on first track
(2) Dial back the updates in useCurrentTrack
2020-08-25 23:34:35 +02:00
Lei Nelissen
edce0329cf Setup scheme to work correctly with shortened product name 2020-08-25 23:33:56 +02:00
Lei Nelissen
be40d8a404 Add BMD Logo to README 2020-08-25 11:01:31 +02:00
Lei Nelissen
9138946ba9 Release TestFlight URL 2020-08-25 10:51:11 +02:00
Lei Nelissen
ea11a7d317 Resolve dark mode styling issues 2020-08-25 10:39:21 +02:00
Lei Nelissen
91344300c8 Update fastlane to support signing and TestFlight 2020-08-25 10:39:02 +02:00
Lei Nelissen
52146a6e12 Disable codec/bitrate settings
Appearently neither Jellyfin nor Emby support user-defined bitrates (see https://emby.media/community/index.php?/topic/65896-change-audio-bitrate-of-transcoder/ and https://github.com/jellyfin/jellyfin/issues/1124. These issues must be fixed first before any attempt at user control over bitrate and codecs can be considered.
2020-08-09 18:29:17 +02:00
Lei Nelissen
0712e6b8ca Add onboarding component 2020-08-09 17:49:36 +02:00
Lei Nelissen
2155320f6b Add fastlane to README and simplify structure 2020-08-09 13:32:20 +02:00
Lei Nelissen
4ff1e173ba Get short hash in separate step 2020-07-28 12:09:15 +02:00
Lei Nelissen
c1f6a2984a Fix variable name in artifact upload 2020-07-28 12:03:37 +02:00
Lei Nelissen
8ba3080734 Fix entry file for good
(Famous last words)
2020-07-28 11:52:09 +02:00
Lei Nelissen
fe22661036 Add commit hash to Android APK filename 2020-07-28 11:43:06 +02:00
Lei Nelissen
3762d9166b Update fastlane syntax 2020-07-28 11:42:43 +02:00
Lei Nelissen
48dd8c23aa Change entrypoint file so iOS bundle is automatically generated 2020-07-28 11:42:08 +02:00
Lei Nelissen
86a4d11f67 Fix small dark mode issues 2020-07-26 17:18:15 +02:00
Lei Nelissen
57b79bf4e2 Implement dark mode for Android 2020-07-26 17:02:18 +02:00
Lei Nelissen
9c24dede18 Make sure dark mode updates are propagated 2020-07-26 14:55:13 +02:00
Lei Nelissen
6978a4dfea Add iOS Dark Mode 2020-07-26 14:45:32 +02:00
Lei Nelissen
ea91b083c3 Adjust slider color on Android 2020-07-26 12:55:02 +02:00
Lei Nelissen
715e9e3580 Adjust podfile for RN 0.63 2020-07-26 12:54:52 +02:00
Lei Nelissen
f1a084bc1e Fix ScrollView padding issues 2020-07-26 12:34:00 +02:00
Lei Nelissen
425b19734c Give artifact a name 2020-07-26 12:30:07 +02:00
Lei Nelissen
60bbbc7193 Update dependencies 2020-07-26 12:29:27 +02:00
Lei Nelissen
8aafd3f0d6 Install and setup bundler correctly 2020-07-26 12:14:42 +02:00
Lei Nelissen
fde4d7ecb3 Speed up fastlane install and upload correct artifact 2020-07-26 12:11:47 +02:00
Lei Nelissen
db2801d77d Add android APK workflow 2020-07-26 11:56:04 +02:00
Lei Nelissen
218c72d9e8 Setup fastlane for both iOS and Android 2020-07-26 11:55:46 +02:00
Lei Nelissen
4d3f6f30d1 Fix Android compilation issues and icon 2020-07-26 11:31:56 +02:00
Lei Nelissen
90a61f258d Use correct lane 2020-07-25 21:26:37 +02:00
Lei Nelissen
3a1fcd4594 Fix syntax 2020-07-25 21:19:50 +02:00
Lei Nelissen
b9501265f9 Cocoapods is already installed, so we don't need to explicitly install it 2020-07-25 21:18:14 +02:00
Lei Nelissen
3680cad5fc Setup Github action generating IPA file 2020-07-25 21:10:40 +02:00
Lei Nelissen
0690aae374 Implement fastlane 2020-07-25 21:10:28 +02:00
100 changed files with 7853 additions and 5875 deletions

55
.github/workflows/fastlane.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Fastlane
on: [push]
jobs:
# build-ios:
# runs-on: macos-latest
# steps:
# - uses: actions/checkout@v1
# - name: Install Node dependencies
# run: npm install
# - name: Install CocoaPods dependencies
# run: pod install --project-directory=./ios
# - name: Run fastlane setup
# env:
# APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
# TEAM_ID: ${{ secrets.TEAM_ID }}
# run: |
# cd ios
# fastlane beta --verbose
# - name: Upload artifact
# uses: actions/upload-artifact@v2
# with:
# name: my-artifact
# path: output/*.ipa
build-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set outputs
id: vars
run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
- name: Set up Ruby 2.6
uses: actions/setup-ruby@v1
with:
ruby-version: 2.6.x
- name: Install fastlane
run: |
gem install bundler
bundle install -j 6
- name: Install Node dependencies
run: npm install
- name: Generate APK
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_URL: ${{ secrets.SENTRY_URL }}
run: fastlane android beta
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: jellyfin-audio-player-android-${{ steps.vars.outputs.sha_short }}.apk
path: android/app/build/outputs/**/*.apk

8
.gitignore vendored
View File

@@ -61,3 +61,11 @@ buck-out/
# CocoaPods
/ios/Pods/
build/
fastlane/report.xml
fastlane/Appfile
certificates/
.env
sentry.properties

12
Gemfile Normal file
View File

@@ -0,0 +1,12 @@
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
# gem "rails"
gem "fastlane", "~> 2.153"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)

203
Gemfile.lock Normal file
View File

@@ -0,0 +1,203 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.421.0)
aws-sdk-core (3.111.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.41.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.87.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.3)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.1)
excon (0.78.1)
faraday (1.3.0)
faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-net_http (1.0.1)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.1)
fastlane (2.172.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 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
commander-fastlane (>= 4.4.6, < 5.0.0)
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)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
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.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-sentry (1.8.0)
gh_inspector (1.1.3)
google-api-client (0.38.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-apis-core (0.2.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.14)
httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
rexml
signet (~> 0.14)
webrick
google-apis-iamcredentials_v1 (0.1.0)
google-apis-core (~> 0.1)
google-apis-storage_v1 (0.1.0)
google-apis-core (~> 0.1)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
google-cloud-storage (1.30.0)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.15.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.5.1)
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.0.2)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
os (1.1.1)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.3)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.4)
rouge (2.0.7)
ruby2_keywords (0.0.4)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.1)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane (~> 2.153)
fastlane-plugin-sentry
BUNDLED WITH
1.17.2

View File

@@ -1,25 +1,69 @@
# Jellyfin Audio Player
This is a React Native-based audio streaming app for Jellyfin.
![Fastlane](https://github.com/leinelissen/jellyfin-audio-player/workflows/Fastlane/badge.svg)
![MIT License](https://img.shields.io/github/license/leinelissen/jellyfin-audio-player)
This is a [React Native](https://reactnative.dev/)-based audio streaming app for [Jellyfin](https://jellyfin.org/). Jellyfin is a community-based piece of software that allows you to stream your media library over the internet. By means of React Native, Jellyfin Audio Player allows you to stream your Jellyfin Music library, with full support for background audio and casting (ie. Airplay and Chromecast).
## ❗Now open for beta testing on iOS
Please follow this link to enroll for the TestFlight beta release of Jellyfin Audio Player: https://testflight.apple.com/join/cf2AMDpx.
|![](./docs/images/now-playing.png)|![](./docs/images/recent-albums.png)|![](./docs/images/album-list.png)|![](./docs/images/search.png)|
|-|-|-|-|
## Features
* Sorting by recent albums
* Browsing through all available albums
* Searching based on album and artist names
* Queuing tracks and albums
* AirPlay and Chromecast support
* Background audio
* Native Dark Mode
### Features being considered
* Downloading music for offline playback
* Searching based on track names
* Looping and shuffling queue
## Getting Started
What you want get started on depends on your intentions. As there is no build for general availability yet, you will need to build this project yourself.
This piece of software is in alpha. I am working on getting this app in ~~TestFlight and~~ Google Play Developer Console, but this is contingent on keys being available. In the meantime, IPAs and APK are intermittenly released on the [Releases page](https://github.com/leinelissen/jellyfin-audio-player/releases). Alternatively, you can build this app from source using the build instructions.
### Using the app
You will need to setup your Jellyfin account for the application to be able to pull in all your audio. To do this, go over to the "Settings" tab and click the "Set Jellyfin server"-button. A modal will pop up in which you will enter your Jellyfin server url, after which you enter your credentials in the provided browser view. When the app detects your credentials, they will automatically be remembered by the app.
You will need to setup your Jellyfin account for the application to be able to pull in all your audio. To do this, go over to the "Settings" tab and click the "Set Jellyfin server"-button. A modal will pop up in which you will enter your Jellyfin server url, after which you enter your credentials in the provided browser view. When the app detects your credentials, they will automatically be remembered for the future.
## Building from source
### Prerequisites
This project is built on React Native, and first of all requires [NodeJS](https://nodejs.org/en/) to be installed. After installing it and cloning this repository, don't forget ton run `npm install` on your command line, so that all Node dependencies are installed.
#### iOS Prerequisites
[XCode](https://developer.apple.com/download/) is required to build the iOS application. It also comes bundles with iOS simulators which make development exceedingly easy. This does mean that iOS development is limited to macs.
#### Android prerequisites
[Android Studio](https://developer.android.com/studio/install) is recommended for development as it includes the Android SDK as well as Android Simulators for devleopment. At the very least, installing the Android SDK is neccessary for building any version of the app.
### Development Build
This app has been mainly developed for iOS, but should mostly function on Android as well. To get started, do the following:
1. Clone this repository
2. Install [NodeJS](https://nodejs.org/en/) and [XCode](https://developer.apple.com/download/)
3. `npm install`
4. `npm run ios`
As soon as all prerequisites are covered, you can start development in either iOS or Android simulators by running the following
```
npm run ios
npm run android
```
### Production Build
Follow step 1-3 from the development build, then do the following:
This project is configured using [Fastlane](https://docs.fastlane.tools/), which allows for easy IPA and APK generation. To get started with this, make sure you install Fastlane first either using bundler (see below), or alternatively via e.g. Homebrew ([see supported methods](https://docs.fastlane.tools/getting-started/ios/setup/)).
```
npm run build:ios
gem install bundler
bundle install -j 6
```
Then open `ios/JellyfinAudioPlayer.xcworkspace` and build the project in XCode.
When fastlane is setup, you can run either commands for generating IPA (iOS) or APK (Android) bundles.
```
fastlane ios beta
fastlane android beta
```
## Licensing and Credits
This work is licensed under the MIT license and was built by Lei Nelissen.
<a href="https://bmd.studio">
<img src="./docs/images/bmd-logo-icon.png" alt="BMD Studio" width="150" height="150" />
</a>
An Apple Developer license, as well as various forms of support are graciously provided for by [BMD Studio](https://bmd.studio), experts in finding creative solutions for difficult problems.

View File

@@ -79,9 +79,11 @@ import com.android.build.OutputFile
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
entryFile: 'index.js'
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/@sentry/react-native/sentry.gradle"
/**
* Set this to true to create two separate APKs instead of one:
@@ -134,6 +136,7 @@ android {
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
multiDexEnabled true
}
splits {
abi {
@@ -164,13 +167,6 @@ android {
}
}
packagingOptions {
pickFirst "lib/armeabi-v7a/libc++_shared.so"
pickFirst "lib/arm64-v8a/libc++_shared.so"
pickFirst "lib/x86/libc++_shared.so"
pickFirst "lib/x86_64/libc++_shared.so"
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->

View File

@@ -9,6 +9,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -1,9 +1,8 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:textColor">#000000</item>
<!-- <item name="android:textColor">#000000</item> -->
</style>
</resources>

View File

@@ -25,4 +25,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.33.1
FLIPPER_VERSION=0.54.0

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,4 +1,4 @@
{
"name": "JellyfinAudioPlayer",
"displayName": "Audio Player"
"displayName": "Jellyfin Player"
}

View File

@@ -1,19 +1,36 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: [
'module:metro-react-native-babel-preset',
],
plugins: [
[
'module-resolver',
{
// root: ['./src'],
root: ['.'],
extensions: [
'.ios.ts',
'.android.ts',
'.ts',
'.ios.tsx',
'.android.tsx',
'.tsx',
'.jsx',
'.js',
'.json',
],
alias: {
store: './src/store',
components: './src/components',
utility: './src/utility',
screens: './src/screens',
assets: './src/assets',
'@localisation': './src/localisation',
CONSTANTS: './src/CONSTANTS',
}
}
],
[
'module:react-native-dotenv'
]
]
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

4
fastlane/Appfile Normal file
View File

@@ -0,0 +1,4 @@
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
apple_id("lei@moeilijkedingen.nl")
team_id("238P3C58WC")

86
fastlane/Fastfile Normal file
View File

@@ -0,0 +1,86 @@
default_platform(:ios)
platform :ios do
lane :alpha do
get_certificates(
development: true,
output_path: 'certificates/'
)
build_app(
scheme: "Jellyfin Player",
export_method: "development",
output_directory: "build",
workspace: "ios/JellyfinAudioPlayer.xcworkspace"
)
end
lane :beta do
get_certificates(
output_path: 'certificates/'
)
get_provisioning_profile(
output_path: 'certificates/',
filename: "provisioning.mobileprovision",
fail_on_name_taken: true,
)
update_code_signing_settings(
use_automatic_signing: true,
path: "ios/JellyfinAudioPlayer.xcodeproj"
)
increment_build_number(
xcodeproj: "ios/JellyfinAudioPlayer.xcodeproj"
)
build_app(
scheme: "Jellyfin Player",
output_directory: "build",
workspace: "ios/JellyfinAudioPlayer.xcworkspace",
export_method: "app-store",
)
upload_to_testflight
build_number = get_build_number(
xcodeproj: "ios/JellyfinAudioPlayer.xcodeproj"
)
Dir.chdir("..") do
sh(
"npx", "react-native", "bundle",
"--dev", "false",
"--platform", "ios",
"--entry-file", "index.js",
"--bundle-output", "build/index.ios.bundle",
"--sourcemap-output", "build/index.ios.bundle.map"
)
end
sentry_create_release(
version: "1.0+#{build_number}",
app_identifier: 'nl.moeilijkedingen.jellyfinaudioplayer',
finalize: true
)
sentry_upload_dsym(
dsym_path: 'build/Jellyfin Player.app.dSYM.zip',
info_plist: 'ios/JellyfinAudioPlayer/Info.plist',
)
sentry_upload_file(
version: "1.0+#{build_number}",
app_identifier: 'nl.moeilijkedingen.jellyfinaudioplayer',
dist: build_number,
file: 'build/index.ios.bundle',
)
sentry_upload_sourcemap(
version: "1.0+#{build_number}",
app_identifier: 'nl.moeilijkedingen.jellyfinaudioplayer',
dist: build_number,
sourcemap: 'build/index.ios.bundle.map',
rewrite: true
)
end
end
platform :android do
desc "Generate beta build"
lane :beta do
gradle(
task: "clean assembleRelease",
project_dir: "android"
)
end
end

5
fastlane/Pluginfile Normal file
View File

@@ -0,0 +1,5 @@
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-sentry'

43
fastlane/README.md Normal file
View File

@@ -0,0 +1,43 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew install fastlane`
# Available Actions
## iOS
### ios alpha
```
fastlane ios alpha
```
### ios beta
```
fastlane ios beta
```
----
## Android
### android beta
```
fastlane android beta
```
Generate beta build
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View File

@@ -1,18 +1,17 @@
// import React from 'react';
// // if (process.env.NODE_ENV === 'development') {
// // const whyDidYouRender = require('@welldone-software/why-did-you-render');
// // whyDidYouRender(React, {
// // trackAllPureComponents: true,
// // });
// // }
import 'react-native-gesture-handler';
import { AppRegistry } from 'react-native';
import TrackPlayer from 'react-native-track-player';
import App from './src/components/App';
import { name as appName } from './app.json';
import PlaybackService from './src/utility/PlaybackService';
import * as Sentry from '@sentry/react-native';
import { SENTRY_DSN } from '@env';
if (SENTRY_DSN) {
Sentry.init({
dsn: SENTRY_DSN
});
}
AppRegistry.registerComponent(appName, () => App);
TrackPlayer.registerPlaybackService(() => PlaybackService);

8
ios/File.swift Normal file
View File

@@ -0,0 +1,8 @@
//
// File.swift
// JellyfinAudioPlayer
//
// Created by Lei Nelissen on 02/11/2020.
//
import Foundation

View File

@@ -0,0 +1,4 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -12,15 +12,9 @@
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* JellyfinAudioPlayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* JellyfinAudioPlayerTests.m */; };
463612208457EBB4B723000A /* libPods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 842AB597D56E84A4ACDC4735 /* libPods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.a */; };
4F46F441249A56FF00308470 /* main.jsbundle in Resources */ = {isa = PBXBuildFile; fileRef = 008F07F21AC5B25A0029DE68 /* main.jsbundle */; };
9C3FCC29E5AD02738E8E3F28 /* libPods-JellyfinAudioPlayer-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D43C7610851B9666193E3F6 /* libPods-JellyfinAudioPlayer-tvOSTests.a */; };
4FA1B23D2550A94C007A035E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA1B23C2550A94C007A035E /* File.swift */; };
A807E2BB233D6F9347D8A95C /* libPods-JellyfinAudioPlayer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 71370E61E2CC6BD9372ADCF3 /* libPods-JellyfinAudioPlayer.a */; };
A9112B4690DCDB63E46B6C30 /* libPods-JellyfinAudioPlayer-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E35451F7979C52C1692C4C9F /* libPods-JellyfinAudioPlayer-tvOS.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -31,17 +25,9 @@
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = JellyfinAudioPlayer;
};
2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7;
remoteInfo = "JellyfinAudioPlayer-tvOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; };
00E356EE1AD99517003FC87E /* JellyfinAudioPlayerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JellyfinAudioPlayerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* JellyfinAudioPlayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JellyfinAudioPlayerTests.m; sourceTree = "<group>"; };
@@ -53,9 +39,9 @@
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = JellyfinAudioPlayer/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = JellyfinAudioPlayer/main.m; sourceTree = "<group>"; };
2710519FCC41B05FDE6738DF /* Pods-JellyfinAudioPlayer.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinAudioPlayer.release.xcconfig"; path = "Target Support Files/Pods-JellyfinAudioPlayer/Pods-JellyfinAudioPlayer.release.xcconfig"; sourceTree = "<group>"; };
2D02E47B1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinAudioPlayer-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2D02E4901E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "JellyfinAudioPlayer-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
39572B38534BBDBB596C8C95 /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.release.xcconfig"; path = "Target Support Files/Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests/Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.release.xcconfig"; sourceTree = "<group>"; };
4FA1B23B2550A94B007A035E /* JellyfinAudioPlayer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JellyfinAudioPlayer-Bridging-Header.h"; sourceTree = "<group>"; };
4FA1B23C2550A94C007A035E /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
590BEA7DE65819C5B5FDAD06 /* Pods-JellyfinAudioPlayer-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinAudioPlayer-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-JellyfinAudioPlayer-tvOSTests/Pods-JellyfinAudioPlayer-tvOSTests.release.xcconfig"; sourceTree = "<group>"; };
71370E61E2CC6BD9372ADCF3 /* libPods-JellyfinAudioPlayer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JellyfinAudioPlayer.a"; sourceTree = BUILT_PRODUCTS_DIR; };
7D43C7610851B9666193E3F6 /* libPods-JellyfinAudioPlayer-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JellyfinAudioPlayer-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -87,22 +73,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E4781E0B4A5D006451C7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A9112B4690DCDB63E46B6C30 /* libPods-JellyfinAudioPlayer-tvOS.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E48D1E0B4A5D006451C7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9C3FCC29E5AD02738E8E3F28 /* libPods-JellyfinAudioPlayer-tvOSTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -126,13 +96,14 @@
13B07FAE1A68108700A75B9A /* JellyfinAudioPlayer */ = {
isa = PBXGroup;
children = (
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
4FA1B23C2550A94C007A035E /* File.swift */,
4FA1B23B2550A94B007A035E /* JellyfinAudioPlayer-Bridging-Header.h */,
);
name = JellyfinAudioPlayer;
sourceTree = "<group>";
@@ -192,8 +163,6 @@
children = (
13B07F961A680F5B00A75B9A /* Jellyfin Player.app */,
00E356EE1AD99517003FC87E /* JellyfinAudioPlayerTests.xctest */,
2D02E47B1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOS.app */,
2D02E4901E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOSTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -209,6 +178,7 @@
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
BDE784ECF29EF861DBFF49D7 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -230,6 +200,8 @@
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */,
1DC46C84C90B4D84A18AC142 /* Upload Debug Symbols to Sentry */,
);
buildRules = (
);
@@ -240,45 +212,6 @@
productReference = 13B07F961A680F5B00A75B9A /* Jellyfin Player.app */;
productType = "com.apple.product-type.application";
};
2D02E47A1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "JellyfinAudioPlayer-tvOS" */;
buildPhases = (
05D2621E5320B14963E6BA49 /* [CP] Check Pods Manifest.lock */,
FD10A7F122414F3F0027D42C /* Start Packager */,
2D02E4771E0B4A5D006451C7 /* Sources */,
2D02E4781E0B4A5D006451C7 /* Frameworks */,
2D02E4791E0B4A5D006451C7 /* Resources */,
2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */,
);
buildRules = (
);
dependencies = (
);
name = "JellyfinAudioPlayer-tvOS";
productName = "JellyfinAudioPlayer-tvOS";
productReference = 2D02E47B1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOS.app */;
productType = "com.apple.product-type.application";
};
2D02E48F1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOSTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "JellyfinAudioPlayer-tvOSTests" */;
buildPhases = (
BFD53620BC35198B520DBD0A /* [CP] Check Pods Manifest.lock */,
2D02E48C1E0B4A5D006451C7 /* Sources */,
2D02E48D1E0B4A5D006451C7 /* Frameworks */,
2D02E48E1E0B4A5D006451C7 /* Resources */,
);
buildRules = (
);
dependencies = (
2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */,
);
name = "JellyfinAudioPlayer-tvOSTests";
productName = "JellyfinAudioPlayer-tvOSTests";
productReference = 2D02E4901E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOSTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -289,24 +222,15 @@
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
DevelopmentTeam = HD2D35G9Y4;
DevelopmentTeam = 238P3C58WC;
ProvisioningStyle = Automatic;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
13B07F861A680F5B00A75B9A = {
DevelopmentTeam = HD2D35G9Y4;
LastSwiftMigration = 1120;
};
2D02E47A1E0B4A5D006451C7 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = HD2D35G9Y4;
DevelopmentTeam = 238P3C58WC;
LastSwiftMigration = 1210;
ProvisioningStyle = Automatic;
};
2D02E48F1E0B4A5D006451C7 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = HD2D35G9Y4;
ProvisioningStyle = Automatic;
TestTargetID = 2D02E47A1E0B4A5D006451C7;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "JellyfinAudioPlayer" */;
@@ -324,8 +248,6 @@
targets = (
13B07F861A680F5B00A75B9A /* JellyfinAudioPlayer */,
00E356ED1AD99517003FC87E /* JellyfinAudioPlayerTests */,
2D02E47A1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOS */,
2D02E48F1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOSTests */,
);
};
/* End PBXProject section */
@@ -342,27 +264,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4F46F441249A56FF00308470 /* main.jsbundle in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E4791E0B4A5D006451C7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E48E1E0B4A5D006451C7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -371,51 +277,50 @@
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Bundle React Native code and images";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/main.jsbundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map\"\nexport NODE_BINARY=$(which node)\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh\n";
};
05D2621E5320B14963E6BA49 /* [CP] Check Pods Manifest.lock */ = {
1DC46C84C90B4D84A18AC142 /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
name = "Upload Debug Symbols to Sentry";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-JellyfinAudioPlayer-tvOS-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym";
};
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JellyfinAudioPlayer/Pods-JellyfinAudioPlayer-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinAudioPlayer/Pods-JellyfinAudioPlayer-resources.sh\"\n";
showEnvVarsInLog = 0;
};
2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native Code And Images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
BBD71961640F29097BE9932A /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -438,26 +343,22 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BFD53620BC35198B520DBD0A /* [CP] Check Pods Manifest.lock */ = {
BDE784ECF29EF861DBFF49D7 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests/Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests-resources.sh",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-JellyfinAudioPlayer-tvOSTests-checkManifestLockResult.txt",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests/Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E68FAF43791AC236CF4BF8CB /* [CP] Check Pods Manifest.lock */ = {
@@ -501,25 +402,6 @@
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
FD10A7F122414F3F0027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -536,27 +418,11 @@
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
4FA1B23D2550A94C007A035E /* File.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E4771E0B4A5D006451C7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */,
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E48C1E0B4A5D006451C7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2DCD954D1E0B4F2C00145EB5 /* JellyfinAudioPlayerTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -565,11 +431,6 @@
target = 13B07F861A680F5B00A75B9A /* JellyfinAudioPlayer */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 2D02E47A1E0B4A5D006451C7 /* JellyfinAudioPlayer-tvOS */;
targetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -589,8 +450,10 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 8DAD3DCD6450C4255A20940E /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = HD2D35G9Y4;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 238P3C58WC;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
@@ -613,9 +476,11 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 39572B38534BBDBB596C8C95 /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = HD2D35G9Y4;
DEVELOPMENT_TEAM = 238P3C58WC;
INFOPLIST_FILE = JellyfinAudioPlayerTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -626,6 +491,8 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
PROVISIONING_PROFILE_SPECIFIER = "";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JellyfinAudioPlayer.app/JellyfinAudioPlayer";
};
name = Release;
@@ -636,8 +503,9 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = HD2D35G9Y4;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 238P3C58WC;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
@@ -650,8 +518,10 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
PRODUCT_NAME = "Jellyfin Player";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "JellyfinAudioPlayer-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -664,8 +534,9 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = HD2D35G9Y4;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_TEAM = 238P3C58WC;
INFOPLIST_FILE = JellyfinAudioPlayer/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
@@ -673,127 +544,16 @@
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
PRODUCT_NAME = "Jellyfin Player";
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "JellyfinAudioPlayer-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
2D02E4971E0B4A5E006451C7 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = DEB1E90737138C4BD6E5323D /* Pods-JellyfinAudioPlayer-tvOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = HD2D35G9Y4;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "JellyfinAudioPlayer-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.JellyfinAudioPlayer-tvOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.2;
};
name = Debug;
};
2D02E4981E0B4A5E006451C7 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B179EFCC51AF0F281E73F0B8 /* Pods-JellyfinAudioPlayer-tvOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = HD2D35G9Y4;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "JellyfinAudioPlayer-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.JellyfinAudioPlayer-tvOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.2;
};
name = Release;
};
2D02E4991E0B4A5E006451C7 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 86AD8F7165272727927424B9 /* Pods-JellyfinAudioPlayer-tvOSTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = HD2D35G9Y4;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "JellyfinAudioPlayer-tvOSTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.JellyfinAudioPlayer-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JellyfinAudioPlayer-tvOS.app/JellyfinAudioPlayer-tvOS";
TVOS_DEPLOYMENT_TARGET = 10.1;
};
name = Debug;
};
2D02E49A1E0B4A5E006451C7 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 590BEA7DE65819C5B5FDAD06 /* Pods-JellyfinAudioPlayer-tvOSTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = HD2D35G9Y4;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "JellyfinAudioPlayer-tvOSTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.JellyfinAudioPlayer-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JellyfinAudioPlayer-tvOS.app/JellyfinAudioPlayer-tvOS";
TVOS_DEPLOYMENT_TARGET = 10.1;
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -822,10 +582,12 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -841,7 +603,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = (
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
@@ -850,6 +612,7 @@
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "Jellyfin Player";
SDKROOT = iphoneos;
};
name = Debug;
@@ -882,10 +645,12 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -894,7 +659,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
LIBRARY_SEARCH_PATHS = (
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
@@ -902,6 +667,7 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_NAME = "Jellyfin Player";
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
@@ -928,24 +694,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "JellyfinAudioPlayer-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D02E4971E0B4A5E006451C7 /* Debug */,
2D02E4981E0B4A5E006451C7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "JellyfinAudioPlayer-tvOSTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D02E4991E0B4A5E006451C7 /* Debug */,
2D02E49A1E0B4A5E006451C7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "JellyfinAudioPlayer" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1130"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "JellyfinAudioPlayer-tvOS.app"
BlueprintName = "JellyfinAudioPlayer-tvOS"
ReferencedContainer = "container:JellyfinAudioPlayer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
BuildableName = "JellyfinAudioPlayer-tvOSTests.xctest"
BlueprintName = "JellyfinAudioPlayer-tvOSTests"
ReferencedContainer = "container:JellyfinAudioPlayer.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "JellyfinAudioPlayer-tvOS.app"
BlueprintName = "JellyfinAudioPlayer-tvOS"
ReferencedContainer = "container:JellyfinAudioPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "JellyfinAudioPlayer-tvOS.app"
BlueprintName = "JellyfinAudioPlayer-tvOS"
ReferencedContainer = "container:JellyfinAudioPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -36,7 +36,11 @@ static void InitializeFlipper(UIApplication *application) {
moduleName:@"JellyfinAudioPlayer"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];

View File

@@ -21,7 +21,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>7</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
@@ -42,7 +42,7 @@
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>processing</string>
<string>fetch</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>

View File

@@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>7</string>
</dict>
</plist>

View File

@@ -1,104 +1,24 @@
platform :ios, '10.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
def add_flipper_pods!(versions = {})
versions['Flipper'] ||= '~> 0.33.1'
versions['DoubleConversion'] ||= '1.1.7'
versions['Flipper-Folly'] ||= '~> 2.1'
versions['Flipper-Glog'] ||= '0.3.6'
versions['Flipper-PeerTalk'] ||= '~> 0.0.4'
versions['Flipper-RSocket'] ||= '~> 1.0'
pod 'FlipperKit', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FlipperKitLayoutPlugin', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/SKIOSNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FlipperKitReactPlugin', versions['Flipper'], :configuration => 'Debug'
# List all transitive dependencies for FlipperKit pods
# to avoid them being linked in Release builds
pod 'Flipper', versions['Flipper'], :configuration => 'Debug'
pod 'Flipper-DoubleConversion', versions['DoubleConversion'], :configuration => 'Debug'
pod 'Flipper-Folly', versions['Flipper-Folly'], :configuration => 'Debug'
pod 'Flipper-Glog', versions['Flipper-Glog'], :configuration => 'Debug'
pod 'Flipper-PeerTalk', versions['Flipper-PeerTalk'], :configuration => 'Debug'
pod 'Flipper-RSocket', versions['Flipper-RSocket'], :configuration => 'Debug'
pod 'FlipperKit/Core', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/CppBridge', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FBCxxFollyDynamicConvert', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FBDefines', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FKPortForwarding', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FlipperKitHighlightOverlay', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FlipperKitLayoutTextSearchable', versions['Flipper'], :configuration => 'Debug'
pod 'FlipperKit/FlipperKitNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
end
# Post Install processing for Flipper
def flipper_post_install(installer)
installer.pods_project.targets.each do |target|
if target.name == 'YogaKit'
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '4.1'
end
end
end
end
require_relative '../node_modules/react-native/scripts/react_native_pods'
target 'JellyfinAudioPlayer' do
# Pods for JellyfinAudioPlayer
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/'
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
config = use_native_modules!
use_react_native!(:path => config["reactNativePath"])
target 'JellyfinAudioPlayerTests' do
inherit! :complete
# Pods for testing
end
use_native_modules!
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable these next few lines.
add_flipper_pods!
use_flipper!
post_install do |installer|
flipper_post_install(installer)
end
end
target 'JellyfinAudioPlayer-tvOS' do
# Pods for JellyfinAudioPlayer-tvOS
target 'JellyfinAudioPlayer-tvOSTests' do
inherit! :search_paths
# Pods for testing
installer.pods_project.build_configurations.each do |config|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
end
end
end

View File

@@ -3,66 +3,66 @@ PODS:
- CocoaAsyncSocket (7.6.4)
- CocoaLibEvent (1.0.0)
- DoubleConversion (1.1.6)
- FBLazyVector (0.62.2)
- FBReactNativeSpec (0.62.2):
- Folly (= 2018.10.22.00)
- RCTRequired (= 0.62.2)
- RCTTypeSafety (= 0.62.2)
- React-Core (= 0.62.2)
- React-jsi (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- Flipper (0.33.1):
- Flipper-Folly (~> 2.1)
- Flipper-RSocket (~> 1.0)
- FBLazyVector (0.63.4)
- FBReactNativeSpec (0.63.4):
- Folly (= 2020.01.13.00)
- RCTRequired (= 0.63.4)
- RCTTypeSafety (= 0.63.4)
- React-Core (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- Flipper (0.54.0):
- Flipper-Folly (~> 2.2)
- Flipper-RSocket (~> 1.1)
- Flipper-DoubleConversion (1.1.7)
- Flipper-Folly (2.2.0):
- Flipper-Folly (2.3.0):
- boost-for-react-native
- CocoaLibEvent (~> 1.0)
- Flipper-DoubleConversion
- Flipper-Glog
- OpenSSL-Universal (= 1.0.2.19)
- OpenSSL-Universal (= 1.0.2.20)
- Flipper-Glog (0.3.6)
- Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.1.0):
- Flipper-Folly (~> 2.2)
- FlipperKit (0.33.1):
- FlipperKit/Core (= 0.33.1)
- FlipperKit/Core (0.33.1):
- Flipper (~> 0.33.1)
- FlipperKit (0.54.0):
- FlipperKit/Core (= 0.54.0)
- FlipperKit/Core (0.54.0):
- Flipper (~> 0.54.0)
- FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines
- FlipperKit/FKPortForwarding
- FlipperKit/CppBridge (0.33.1):
- Flipper (~> 0.33.1)
- FlipperKit/FBCxxFollyDynamicConvert (0.33.1):
- Flipper-Folly (~> 2.1)
- FlipperKit/FBDefines (0.33.1)
- FlipperKit/FKPortForwarding (0.33.1):
- FlipperKit/CppBridge (0.54.0):
- Flipper (~> 0.54.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.54.0):
- Flipper-Folly (~> 2.2)
- FlipperKit/FBDefines (0.54.0)
- FlipperKit/FKPortForwarding (0.54.0):
- CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.33.1)
- FlipperKit/FlipperKitLayoutPlugin (0.33.1):
- FlipperKit/FlipperKitHighlightOverlay (0.54.0)
- FlipperKit/FlipperKitLayoutPlugin (0.54.0):
- FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.33.1)
- FlipperKit/FlipperKitNetworkPlugin (0.33.1):
- FlipperKit/FlipperKitLayoutTextSearchable (0.54.0)
- FlipperKit/FlipperKitNetworkPlugin (0.54.0):
- FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.33.1):
- FlipperKit/FlipperKitReactPlugin (0.54.0):
- FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.33.1):
- FlipperKit/FlipperKitUserDefaultsPlugin (0.54.0):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.33.1):
- FlipperKit/SKIOSNetworkPlugin (0.54.0):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- Folly (2018.10.22.00):
- Folly (2020.01.13.00):
- boost-for-react-native
- DoubleConversion
- Folly/Default (= 2018.10.22.00)
- Folly/Default (= 2020.01.13.00)
- glog
- Folly/Default (2018.10.22.00):
- Folly/Default (2020.01.13.00):
- boost-for-react-native
- DoubleConversion
- glog
@@ -76,263 +76,277 @@ PODS:
- libwebp/mux (1.1.0):
- libwebp/demux
- libwebp/webp (1.1.0)
- OpenSSL-Universal (1.0.2.19):
- OpenSSL-Universal/Static (= 1.0.2.19)
- OpenSSL-Universal/Static (1.0.2.19)
- RCTRequired (0.62.2)
- RCTTypeSafety (0.62.2):
- FBLazyVector (= 0.62.2)
- Folly (= 2018.10.22.00)
- RCTRequired (= 0.62.2)
- React-Core (= 0.62.2)
- React (0.62.2):
- React-Core (= 0.62.2)
- React-Core/DevSupport (= 0.62.2)
- React-Core/RCTWebSocket (= 0.62.2)
- React-RCTActionSheet (= 0.62.2)
- React-RCTAnimation (= 0.62.2)
- React-RCTBlob (= 0.62.2)
- React-RCTImage (= 0.62.2)
- React-RCTLinking (= 0.62.2)
- React-RCTNetwork (= 0.62.2)
- React-RCTSettings (= 0.62.2)
- React-RCTText (= 0.62.2)
- React-RCTVibration (= 0.62.2)
- React-Core (0.62.2):
- Folly (= 2018.10.22.00)
- OpenSSL-Universal (1.0.2.20):
- OpenSSL-Universal/Static (= 1.0.2.20)
- OpenSSL-Universal/Static (1.0.2.20)
- RCTRequired (0.63.4)
- RCTTypeSafety (0.63.4):
- FBLazyVector (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTRequired (= 0.63.4)
- React-Core (= 0.63.4)
- React (0.63.4):
- React-Core (= 0.63.4)
- React-Core/DevSupport (= 0.63.4)
- React-Core/RCTWebSocket (= 0.63.4)
- React-RCTActionSheet (= 0.63.4)
- React-RCTAnimation (= 0.63.4)
- React-RCTBlob (= 0.63.4)
- React-RCTImage (= 0.63.4)
- React-RCTLinking (= 0.63.4)
- React-RCTNetwork (= 0.63.4)
- React-RCTSettings (= 0.63.4)
- React-RCTText (= 0.63.4)
- React-RCTVibration (= 0.63.4)
- React-callinvoker (0.63.4)
- React-Core (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default (= 0.62.2)
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-Core/Default (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/CoreModulesHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/CoreModulesHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/Default (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/Default (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/DevSupport (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/DevSupport (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default (= 0.62.2)
- React-Core/RCTWebSocket (= 0.62.2)
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-jsinspector (= 0.62.2)
- React-Core/Default (= 0.63.4)
- React-Core/RCTWebSocket (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- React-jsinspector (= 0.63.4)
- Yoga
- React-Core/RCTActionSheetHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTActionSheetHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTAnimationHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTAnimationHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTBlobHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTBlobHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTImageHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTImageHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTLinkingHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTLinkingHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTNetworkHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTNetworkHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTSettingsHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTSettingsHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTTextHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTTextHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTVibrationHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTVibrationHeaders (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-Core/RCTWebSocket (0.62.2):
- Folly (= 2018.10.22.00)
- React-Core/RCTWebSocket (0.63.4):
- Folly (= 2020.01.13.00)
- glog
- React-Core/Default (= 0.62.2)
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsiexecutor (= 0.62.2)
- React-Core/Default (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsiexecutor (= 0.63.4)
- Yoga
- React-CoreModules (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- RCTTypeSafety (= 0.62.2)
- React-Core/CoreModulesHeaders (= 0.62.2)
- React-RCTImage (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-cxxreact (0.62.2):
- React-CoreModules (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/CoreModulesHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- React-RCTImage (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-cxxreact (0.63.4):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- Folly (= 2020.01.13.00)
- glog
- React-jsinspector (= 0.62.2)
- React-jsi (0.62.2):
- React-callinvoker (= 0.63.4)
- React-jsinspector (= 0.63.4)
- React-jsi (0.63.4):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- Folly (= 2020.01.13.00)
- glog
- React-jsi/Default (= 0.62.2)
- React-jsi/Default (0.62.2):
- React-jsi/Default (= 0.63.4)
- React-jsi/Default (0.63.4):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- Folly (= 2020.01.13.00)
- glog
- React-jsiexecutor (0.62.2):
- React-jsiexecutor (0.63.4):
- DoubleConversion
- Folly (= 2018.10.22.00)
- Folly (= 2020.01.13.00)
- glog
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- React-jsinspector (0.62.2)
- react-native-safe-area-context (3.0.7):
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsinspector (0.63.4)
- react-native-appearance (0.3.4):
- React
- react-native-slider (3.0.0):
- react-native-safe-area-context (3.1.9):
- React-Core
- react-native-slider (3.0.3):
- React
- react-native-track-player (1.2.3):
- React
- react-native-webview (10.3.2):
- React
- React-RCTActionSheet (0.62.2):
- React-Core/RCTActionSheetHeaders (= 0.62.2)
- React-RCTAnimation (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- RCTTypeSafety (= 0.62.2)
- React-Core/RCTAnimationHeaders (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-RCTBlob (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- React-Core/RCTBlobHeaders (= 0.62.2)
- React-Core/RCTWebSocket (= 0.62.2)
- React-jsi (= 0.62.2)
- React-RCTNetwork (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-RCTImage (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- RCTTypeSafety (= 0.62.2)
- React-Core/RCTImageHeaders (= 0.62.2)
- React-RCTNetwork (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-RCTLinking (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- React-Core/RCTLinkingHeaders (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-RCTNetwork (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- RCTTypeSafety (= 0.62.2)
- React-Core/RCTNetworkHeaders (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-RCTSettings (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- RCTTypeSafety (= 0.62.2)
- React-Core/RCTSettingsHeaders (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- React-RCTText (0.62.2):
- React-Core/RCTTextHeaders (= 0.62.2)
- React-RCTVibration (0.62.2):
- FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- React-Core/RCTVibrationHeaders (= 0.62.2)
- ReactCommon/turbomodule/core (= 0.62.2)
- ReactCommon/callinvoker (0.62.2):
- react-native-webview (11.2.1):
- React-Core
- React-RCTActionSheet (0.63.4):
- React-Core/RCTActionSheetHeaders (= 0.63.4)
- React-RCTAnimation (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTAnimationHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTBlob (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- React-Core/RCTBlobHeaders (= 0.63.4)
- React-Core/RCTWebSocket (= 0.63.4)
- React-jsi (= 0.63.4)
- React-RCTNetwork (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTImage (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTImageHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- React-RCTNetwork (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTLinking (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- React-Core/RCTLinkingHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTNetwork (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTNetworkHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTSettings (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- RCTTypeSafety (= 0.63.4)
- React-Core/RCTSettingsHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- React-RCTText (0.63.4):
- React-Core/RCTTextHeaders (= 0.63.4)
- React-RCTVibration (0.63.4):
- FBReactNativeSpec (= 0.63.4)
- Folly (= 2020.01.13.00)
- React-Core/RCTVibrationHeaders (= 0.63.4)
- React-jsi (= 0.63.4)
- ReactCommon/turbomodule/core (= 0.63.4)
- ReactCommon/turbomodule/core (0.63.4):
- DoubleConversion
- Folly (= 2018.10.22.00)
- Folly (= 2020.01.13.00)
- glog
- React-cxxreact (= 0.62.2)
- ReactCommon/turbomodule/core (0.62.2):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- React-Core (= 0.62.2)
- React-cxxreact (= 0.62.2)
- React-jsi (= 0.62.2)
- ReactCommon/callinvoker (= 0.62.2)
- RNCAsyncStorage (1.11.0):
- React
- React-callinvoker (= 0.63.4)
- React-Core (= 0.63.4)
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- RNCAsyncStorage (1.12.1):
- React-Core
- RNCMaskedView (0.1.10):
- React
- RNCPicker (1.6.5):
- React
- RNFastImage (8.1.5):
- React
- SDWebImage (~> 5.0)
- SDWebImageWebPCoder (~> 0.4.1)
- RNGestureHandler (1.6.1):
- React
- RNReanimated (1.9.0):
- React
- RNScreens (2.9.0):
- React
- RNCPicker (1.8.1):
- React-Core
- RNFastImage (8.3.4):
- React-Core
- SDWebImage (~> 5.8)
- SDWebImageWebPCoder (~> 0.6.1)
- RNGestureHandler (1.9.0):
- React-Core
- RNLocalize (2.0.1):
- React-Core
- RNReanimated (1.13.2):
- React-Core
- RNScreens (2.17.1):
- React-Core
- RNSentry (2.2.0):
- React-Core
- Sentry (= 6.1.4)
- RNSVG (12.1.0):
- React
- SDWebImage (5.8.1):
- SDWebImage/Core (= 5.8.1)
- SDWebImage/Core (5.8.1)
- SDWebImageWebPCoder (0.4.1):
- SDWebImage (5.10.4):
- SDWebImage/Core (= 5.10.4)
- SDWebImage/Core (5.10.4)
- SDWebImageWebPCoder (0.6.1):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.5)
- SDWebImage/Core (~> 5.7)
- Sentry (6.1.4):
- Sentry/Core (= 6.1.4)
- Sentry/Core (6.1.4)
- Yoga (1.14.0)
- YogaKit (1.18.1):
- Yoga (~> 1.14)
@@ -341,30 +355,31 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
- Flipper (~> 0.33.1)
- Flipper (~> 0.54.0)
- Flipper-DoubleConversion (= 1.1.7)
- Flipper-Folly (~> 2.1)
- Flipper-Folly (~> 2.2)
- Flipper-Glog (= 0.3.6)
- Flipper-PeerTalk (~> 0.0.4)
- Flipper-RSocket (~> 1.0)
- FlipperKit (~> 0.33.1)
- FlipperKit/Core (~> 0.33.1)
- FlipperKit/CppBridge (~> 0.33.1)
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.33.1)
- FlipperKit/FBDefines (~> 0.33.1)
- FlipperKit/FKPortForwarding (~> 0.33.1)
- FlipperKit/FlipperKitHighlightOverlay (~> 0.33.1)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.33.1)
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.33.1)
- FlipperKit/FlipperKitNetworkPlugin (~> 0.33.1)
- FlipperKit/FlipperKitReactPlugin (~> 0.33.1)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.33.1)
- FlipperKit/SKIOSNetworkPlugin (~> 0.33.1)
- Flipper-RSocket (~> 1.1)
- FlipperKit (~> 0.54.0)
- FlipperKit/Core (~> 0.54.0)
- FlipperKit/CppBridge (~> 0.54.0)
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.54.0)
- FlipperKit/FBDefines (~> 0.54.0)
- FlipperKit/FKPortForwarding (~> 0.54.0)
- FlipperKit/FlipperKitHighlightOverlay (~> 0.54.0)
- FlipperKit/FlipperKitLayoutPlugin (~> 0.54.0)
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.54.0)
- FlipperKit/FlipperKitNetworkPlugin (~> 0.54.0)
- FlipperKit/FlipperKitReactPlugin (~> 0.54.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.54.0)
- FlipperKit/SKIOSNetworkPlugin (~> 0.54.0)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
- React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`)
- React-Core (from `../node_modules/react-native/`)
- React-Core/DevSupport (from `../node_modules/react-native/`)
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
@@ -373,6 +388,7 @@ DEPENDENCIES:
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-appearance (from `../node_modules/react-native-appearance`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-track-player (from `../node_modules/react-native-track-player`)
@@ -386,15 +402,16 @@ DEPENDENCIES:
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNCMaskedView (from `../node_modules/@react-native-community/masked-view`)"
- "RNCPicker (from `../node_modules/@react-native-community/picker`)"
- RNFastImage (from `../node_modules/react-native-fast-image`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
- RNSVG (from `../node_modules/react-native-svg`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -414,6 +431,7 @@ SPEC REPOS:
- OpenSSL-Universal
- SDWebImage
- SDWebImageWebPCoder
- Sentry
- YogaKit
EXTERNAL SOURCES:
@@ -433,6 +451,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/Libraries/TypeSafety"
React:
:path: "../node_modules/react-native/"
React-callinvoker:
:path: "../node_modules/react-native/ReactCommon/callinvoker"
React-Core:
:path: "../node_modules/react-native/"
React-CoreModules:
@@ -445,6 +465,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-appearance:
:path: "../node_modules/react-native-appearance"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-slider:
@@ -483,10 +505,14 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-fast-image"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSentry:
:path: "../node_modules/@sentry/react-native"
RNSVG:
:path: "../node_modules/react-native-svg"
Yoga:
@@ -496,56 +522,61 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245
FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e
Flipper: 6c1f484f9a88d30ab3e272800d53688439e50f69
DoubleConversion: cde416483dac037923206447da6e1454df403714
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
Flipper-Folly: e4493b013c02d9347d5e0cb4d128680239f6c78a
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
FlipperKit: 6dc9b8f4ef60d9e5ded7f0264db299c91f18832e
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035
RCTTypeSafety: 93006131180074cffa227a1075802c89a49dd4ce
React: 29a8b1a02bd764fb7644ef04019270849b9a7ac3
React-Core: b12bffb3f567fdf99510acb716ef1abd426e0e05
React-CoreModules: 4a9b87bbe669d6c3173c0132c3328e3b000783d0
React-cxxreact: e65f9c2ba0ac5be946f53548c1aaaee5873a8103
React-jsi: b6dc94a6a12ff98e8877287a0b7620d365201161
React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da
React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493
react-native-safe-area-context: c39fc20a20cd66ebf1d56c6f8b8711142fbfee98
react-native-slider: 05f11678260cb27c3d00a2dd1558b623be3ec8d2
OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd
RCTRequired: 082f10cd3f905d6c124597fd1c14f6f2655ff65e
RCTTypeSafety: 8c9c544ecbf20337d069e4ae7fd9a377aadf504b
React: b0a957a2c44da4113b0c4c9853d8387f8e64e615
React-callinvoker: c3f44dd3cb195b6aa46621fff95ded79d59043fe
React-Core: d3b2a1ac9a2c13c3bcde712d9281fc1c8a5b315b
React-CoreModules: 0581ff36cb797da0943d424f69e7098e43e9be60
React-cxxreact: c1480d4fda5720086c90df537ee7d285d4c57ac3
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
react-native-appearance: fc2014516054585d531e07aa0b40ab0de1d2be85
react-native-safe-area-context: 86612d2c9a9e94e288319262d10b5f93f0b395f5
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
react-native-track-player: ba2416753b58f3cdf9db5a07daa65876d659f925
react-native-webview: e2c0bce9a1a7c7edd4eb30f0c3016fce216245ce
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71
React-RCTImage: e70be9b9c74fe4e42d0005f42cace7981c994ac3
React-RCTLinking: c1b9739a88d56ecbec23b7f63650e44672ab2ad2
React-RCTNetwork: 73138b6f45e5a2768ad93f3d57873c2a18d14b44
React-RCTSettings: 6e3738a87e21b39a8cb08d627e68c44acf1e325a
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
RNCAsyncStorage: d059c3ee71738c39834a627476322a5a8cd5bf36
react-native-webview: dbe6c1ad149740f0e2d84a963f1d3c3e77f2d99c
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
React-RCTImage: c1b1f2d3f43a4a528c8946d6092384b5c880d2f0
React-RCTLinking: 35ae4ab9dc0410d1fcbdce4d7623194a27214fb2
React-RCTNetwork: 29ec2696f8d8cfff7331fac83d3e893c95ef43ae
React-RCTSettings: 60f0691bba2074ef394f95d4c2265ec284e0a46a
React-RCTText: 5c51df3f08cb9dedc6e790161195d12bac06101c
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCPicker: 55b9b4240d0a9eba8733d02616775d4040de2e7d
RNFastImage: 35ae972d6727c84ee3f5c6897e07f84d0a3445e9
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
RNReanimated: b5ccb50650ba06f6e749c7c329a1bc3ae0c88b43
RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da
RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
RNLocalize: dcf0fdb332b37b0d24178e876a7ce4dbbc9c838d
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
RNSentry: 6aeba1adc242fd22a6826acae92f430697b47a9c
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
SDWebImage: e3eae2eda88578db0685a0c88597fdadd9433f05
SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8
Yoga: 3ebccbdd559724312790e7742142d062476b698e
SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
Sentry: 9d055e2de30a77685e86b219acf02e59b82091fc
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 34db80daea4bf9020013707ff198a8e43d5808ec
PODFILE CHECKSUM: d99c1202f98b3f7477b6a86c9226010b36143469
COCOAPODS: 1.9.1
COCOAPODS: 1.10.1

10592
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +1,72 @@
{
"name": "JellyfinAudioPlayer",
"version": "0.0.1",
"main": "src/index.ts",
"main": "src/index.js",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"ios": "react-native run-ios --scheme \"Jellyfin Player\"",
"start": "react-native start",
"test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"build:ios": "react-native bundle --entry-file='index.ts' --bundle-output='./ios/main.jsbundle' --dev=false --platform='ios'"
},
"dependencies": {
"@react-native-community/async-storage": "^1.11.0",
"@react-native-community/async-storage": "^1.12.1",
"@react-native-community/masked-view": "^0.1.10",
"@react-native-community/picker": "^1.6.5",
"@react-native-community/slider": "^3.0.0",
"@react-navigation/bottom-tabs": "^5.6.1",
"@react-navigation/native": "^5.6.1",
"@react-navigation/stack": "^5.6.2",
"@reduxjs/toolkit": "^1.4.0",
"@types/lodash": "^4.14.157",
"@react-native-community/picker": "^1.8.1",
"@react-native-community/slider": "^3.0.3",
"@react-navigation/bottom-tabs": "^5.11.7",
"@react-navigation/native": "^5.9.2",
"@react-navigation/stack": "^5.14.2",
"@reduxjs/toolkit": "^1.5.0",
"@sentry/react-native": "^2.2.0",
"@types/lodash": "^4.14.168",
"@types/redux-logger": "^3.0.8",
"@types/styled-components": "^5.1.0",
"date-fns": "^2.14.0",
"fuse.js": "^6.4.0",
"lodash": "^4.17.15",
"react": "16.11.0",
"react-native": "0.62.2",
"react-native-fast-image": "^8.1.5",
"react-native-gesture-handler": "^1.6.1",
"react-native-reanimated": "^1.9.0",
"react-native-safe-area-context": "^3.0.7",
"react-native-screens": "^2.9.0",
"@types/styled-components": "^5.1.7",
"date-fns": "^2.17.0",
"fuse.js": "^6.4.6",
"i18n-js": "^3.8.0",
"lodash": "^4.17.20",
"react": "^17.0.1",
"react-native": "^0.63.4",
"react-native-appearance": "^0.3.4",
"react-native-dotenv": "^2.5.1",
"react-native-fast-image": "^8.3.4",
"react-native-gesture-handler": "^1.9.0",
"react-native-localize": "^2.0.1",
"react-native-reanimated": "^1.13.2",
"react-native-safe-area-context": "^3.1.9",
"react-native-screens": "^2.17.1",
"react-native-svg": "^12.1.0",
"react-native-svg-transformer": "^0.14.3",
"react-native-track-player": "github:leinelissen/react-native-track-player",
"react-native-webview": "^10.3.2",
"react-redux": "^7.2.0",
"react-native-webview": "^11.2.1",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"styled-components": "^5.1.1"
"styled-components": "^5.2.1"
},
"devDependencies": {
"@babel/core": "^7.10.4",
"@babel/runtime": "^7.10.4",
"@react-native-community/eslint-config": "^1.0.0",
"@types/jest": "^24.0.24",
"@types/react-native": "^0.62.17",
"@types/react-redux": "^7.1.9",
"@types/react-test-renderer": "16.9.2",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"babel-jest": "^24.9.0",
"babel-plugin-module-resolver": "^4.0.0",
"eslint": "^6.5.1",
"eslint-plugin-react-hooks": "^4.0.5",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.58.0",
"react-test-renderer": "16.11.0",
"typescript": "^3.9.6"
"@babel/core": "^7.12.13",
"@babel/runtime": "^7.12.13",
"@react-native-community/eslint-config": "^2.0.0",
"@types/i18n-js": "^3.8.0",
"@types/jest": "^26.0.20",
"@types/react-native": "^0.63.48",
"@types/react-redux": "^7.1.16",
"@types/react-test-renderer": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"babel-jest": "^26.6.3",
"babel-plugin-module-resolver": "^4.1.0",
"eslint": "^7.19.0",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^26.6.3",
"metro-react-native-babel-preset": "^0.65.0",
"react-test-renderer": "^17.0.1",
"typescript": "^4.1.3"
},
"jest": {
"preset": "react-native",

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

1
src/assets/repeat.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 106.69 89.45"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M0,41.16a5.37,5.37,0,0,0,10.74,0V37.94c0-7.76,5.37-12.89,13.38-12.89H60.94v10.2a4.05,4.05,0,0,0,4.39,4.3,5.27,5.27,0,0,0,3.32-1.22L87.11,23.1a4.1,4.1,0,0,0,0-6.55L68.65,1.22A5.57,5.57,0,0,0,65.33,0a4.09,4.09,0,0,0-4.39,4.35v10.1H25.15C9.77,14.45,0,23.29,0,37.16ZM45.75,54.25A4,4,0,0,0,41.36,50a5.25,5.25,0,0,0-3.27,1.17L19.63,66.36a4,4,0,0,0,0,6.54L38.09,88.23a5,5,0,0,0,3.27,1.22,4,4,0,0,0,4.39-4.29V75H81.54c15.38,0,25.15-8.89,25.15-22.7v-4a5.37,5.37,0,1,0-10.74,0v3.22c0,7.72-5.37,12.9-13.38,12.9H45.75Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 695 B

1
src/assets/shuffle.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 114.69 90.97"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Symbols"><g id="Regular-M"><path d="M0,70.51c0,3.12,2.29,5.27,5.76,5.27H16.85c8.44,0,13.47-2.44,19.58-9.52L47.36,53.47l11,12.79c6,7.08,11.08,9.57,19.58,9.57h9V86.67A4,4,0,0,0,91.26,91a5.27,5.27,0,0,0,3.32-1.22L113,74.51A4.08,4.08,0,0,0,113,68L94.58,52.64a5.27,5.27,0,0,0-3.32-1.22,4,4,0,0,0-4.35,4.29v9.48H78.22c-5.08,0-8.2-1.62-12.16-6.25L54.39,45.31,66.06,31.74c4-4.69,7.08-6.3,12.16-6.3h8.69v9.81a4,4,0,0,0,4.35,4.3,5.27,5.27,0,0,0,3.32-1.22L113,23.1a4.09,4.09,0,0,0,0-6.55L94.58,1.22A5.27,5.27,0,0,0,91.26,0a4,4,0,0,0-4.35,4.3V14.84h-9c-8.5,0-13.57,2.49-19.58,9.57l-11,12.8L36.43,24.41c-6.11-7.08-11.14-9.57-19.58-9.57H5.76C2.29,14.84,0,17,0,20.17s2.34,5.32,5.76,5.32H17.09c4.69,0,7.67,1.61,11.67,6.25L40.43,45.31,28.76,58.94c-4.05,4.63-7,6.25-11.67,6.25H5.76C2.34,65.19,0,67.38,0,70.51Z"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 964 B

View File

@@ -2,19 +2,27 @@ import React, { Component } from 'react';
import { Provider } from 'react-redux';
import TrackPlayer from 'react-native-track-player';
import { PersistGate } from 'redux-persist/integration/react';
import { NavigationContainer } from '@react-navigation/native';
import { AppearanceProvider, Appearance, AppearanceListener } from 'react-native-appearance';
import Routes from '../screens';
import store, { persistedStore } from 'store';
import {
NavigationContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
interface State {
isReady: boolean;
colorScheme?: string;
}
export default class App extends Component<State> {
state = {
isReady: false
export default class App extends Component<{}, State> {
state: State = {
isReady: false,
};
subscription = null;
async componentDidMount() {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
@@ -27,11 +35,20 @@ export default class App extends Component<State> {
TrackPlayer.CAPABILITY_SEEK_TO,
]
});
this.setState({ isReady: true });
this.subscription = Appearance.addChangeListener(this.setScheme);
this.setState({ isReady: true, colorScheme: Appearance.getColorScheme() });
}
componentWillUnmount() {
this.subscription?.remove();
}
setScheme: AppearanceListener = ({ colorScheme }) => {
this.setState({ colorScheme });
}
render() {
const { isReady } = this.state;
const { isReady, colorScheme } = this.state;
if (!isReady) {
return null;
@@ -40,9 +57,11 @@ export default class App extends Component<State> {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistedStore}>
<NavigationContainer>
<Routes />
</NavigationContainer>
<AppearanceProvider>
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Routes />
</NavigationContainer>
</AppearanceProvider>
</PersistGate>
</Provider>
);

View File

@@ -0,0 +1,26 @@
import { StyleSheet, PlatformColor } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
export const colors = StyleSheet.create({
text: {
color: PlatformColor('?attr/colorOnBackground'),
},
view: {
backgroundColor: PlatformColor('?android:colorBackground'),
},
border: {
borderColor: '#88888844'
},
activeBackground: {
backgroundColor: `${THEME_COLOR}44`,
},
imageBackground: {
backgroundColor: PlatformColor('?attr/colorBackgroundFloating'),
},
modal: {
backgroundColor: PlatformColor('?attr/colorBackgroundFloating'),
},
input: {
backgroundColor: PlatformColor('?attr/colorBackgroundFloating'),
}
});

View File

@@ -0,0 +1,27 @@
import { StyleSheet, PlatformColor, DynamicColorIOS } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
export const colors = StyleSheet.create({
text: {
color: PlatformColor('label'),
},
view: {
backgroundColor: PlatformColor('systemBackground'),
},
border: {
borderColor: PlatformColor('systemGray5Color'),
},
activeBackground: {
backgroundColor: DynamicColorIOS({ light: `${THEME_COLOR}16`, dark: `${THEME_COLOR}66` })
},
imageBackground: {
backgroundColor: PlatformColor('systemGray5Color')
},
modal: {
backgroundColor: DynamicColorIOS({ light: '#eeeeeeee', dark: '#222222ee' })
},
input: {
backgroundColor: PlatformColor('systemGray5Color'),
color: PlatformColor('label'),
}
});

View File

@@ -2,7 +2,6 @@ import styled from 'styled-components/native';
const Input = styled.TextInput`
margin: 10px 0;
background-color: #f6f6f6;
border-radius: 5px;
padding: 15px;
`;

View File

@@ -3,20 +3,20 @@ import { TouchableOpacityProps, Text } from 'react-native';
import ChevronRight from 'assets/chevron-right.svg';
import styled from 'styled-components/native';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from './Colors';
const BUTTON_SIZE = 14;
const Container = styled.TouchableOpacity`
padding: 18px 0;
border-bottom-width: 1px;
border-bottom-color: #eee;
flex-direction: row;
justify-content: space-between;
`;
const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) => {
return (
<Container {...props}>
<Container {...props} style={colors.border}>
<Text style={{ color: THEME_COLOR, fontSize: 16 }}>{children}</Text>
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={THEME_COLOR} />
</Container>

View File

@@ -1,24 +1,38 @@
import React from 'react';
import styled from 'styled-components/native';
import { SafeAreaView } from 'react-native';
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components/native';
import { SafeAreaView, Pressable } from 'react-native';
import { colors } from './Colors';
import { useNavigation, StackActions } from '@react-navigation/native';
const Background = styled.View`
background-color: #eeeeeeee;
interface Props {
fullSize?: boolean;
}
const Background = styled(Pressable)`
padding: 100px 25px;
flex: 1;
justify-content: center;
`;
const Container = styled.View`
background-color: white;
const Container = styled(Pressable)<Pick<Props, 'fullSize'>>`
border-radius: 20px;
flex: 1;
margin: auto 0;
${props => props.fullSize && css`
flex: 1;
`}
`;
const Modal: React.FC = ({ children }) => {
const Modal: React.FC<Props> = ({ children, fullSize = true }) => {
const navigation = useNavigation();
const closeModal = useCallback(() => {
navigation.dispatch(StackActions.popToTop());
}, [navigation]);
return (
<Background>
<SafeAreaView style={{ flex: 1}}>
<Container>
<Background style={colors.modal} onPress={closeModal}>
<SafeAreaView style={{ flex: 1 }}>
<Container style={colors.view} fullSize={fullSize}>
{children}
</Container>
</SafeAreaView>

View File

@@ -1,24 +1,41 @@
import React, { useCallback } from 'react';
import { TouchableOpacity } from 'react-native';
import { Pressable, ViewStyle } from 'react-native';
interface TouchableHandlerProps {
id: string;
onPress: (id: string) => void;
onLongPress?: (id: string) => void;
}
function TouchableStyles({ pressed }: { pressed: boolean }): ViewStyle {
if (pressed) {
return { opacity: 0.5 };
} else {
return { opacity: 1 };
}
}
/**
* This is a generic handler that accepts id as a prop, and return it when it is
* pressed. This comes in handy with lists in which albums / tracks need to be selected.
*/
const TouchableHandler: React.FC<TouchableHandlerProps> = ({ id, onPress, children }) => {
const TouchableHandler: React.FC<TouchableHandlerProps> = ({ id, onPress, onLongPress, children }) => {
const handlePress = useCallback(() => {
return onPress(id);
}, [id, onPress]);
const handleLongPress = useCallback(() => {
return onLongPress ? onLongPress(id) : undefined;
}, [id, onLongPress]);
return (
<TouchableOpacity onPress={handlePress}>
<Pressable
onPress={handlePress}
onLongPress={handleLongPress}
style={TouchableStyles}
>
{children}
</TouchableOpacity>
</Pressable>
);
};

View File

@@ -4,4 +4,9 @@ export const Header = styled.Text`
margin: 24px 0 12px 0;
font-size: 36px;
font-weight: bold;
`;
export const SubHeader = styled.Text`
font-size: 24px;
margin: 12px 0;
`;

45
src/localisation/index.ts Normal file
View File

@@ -0,0 +1,45 @@
import i18n from 'i18n-js';
import { findBestAvailableLanguage } from 'react-native-localize';
import { LocaleKeys } from './types';
// Lazy loaders for locale
const localeGetters: Record<string, () => object> = {
en: () => require('./lang/en/locale.json'),
fr: () => require('./lang/fr/locale.json'),
nl: () => require('./lang/nl/locale.json'),
es: () => require('./lang/es/locale.json'),
};
// Have RNLocalize pick the best locale from the languages on offer
const locale = findBestAvailableLanguage(Object.keys(localeGetters));
// Check if the locale is correctly picked
if (!locale || !locale.languageTag) {
throw new Error('Invalid locale selected');
}
// Set the key-value pairs for the different languages you want to support.
i18n.translations = {
en: localeGetters.en(),
};
// If the locale is not english, we add it to the translations key s well
if (locale.languageTag !== 'en') {
i18n.translations[locale.languageTag] = localeGetters[locale.languageTag as string]();
}
// Set the locale once at the beginning of your app.
i18n.locale = locale.languageTag;
// Fallback to the default language for missing translation strings
i18n.fallbacks = true;
/**
* An i18n Typescript helper with autocomplete for the key argument
* @param key string
*/
export function t(key: LocaleKeys) {
return i18n.t(key);
}
export default i18n;

View File

@@ -0,0 +1,28 @@
{
"play-next": "Play Next",
"play-album": "Play Album",
"queue": "Queue",
"add-to-queue": "Add to Queue",
"clear-queue": "Clear Queue",
"no-results": "No results...",
"album": "Album",
"albums": "Albums",
"all-albums": "All Albums",
"search": "Search",
"music": "Music",
"now-playing": "Now Playing",
"onboarding-welcome": "Welcome!",
"onboarding-intro": "Jellyfin Audio Player will allow you to stream your music library from anywhere, with full support for background audio and casting.",
"onboarding-cta": "In order to get started, you need a Jellyfin server. Click the button below to enter your Jellyfin server address and login to it.",
"set-jellyfin-server": "Set Jellyfin Server",
"set-jellyfin-server-instruction": "Please enter your Jellyfin server URL. Make sure to include the protocol and port",
"settings": "Settings",
"jellyfin-library": "Jellyfin Library",
"jellyfin-server-url": "Jellyfin Server URL",
"jellyfin-access-token": "Jellyfin Access Token",
"jellyfin-user-id": "Jellyfin User ID",
"setting-cache": "Cache",
"setting-cache-description": "If you have updated your Jellyfin library, but the app is holding on to cached assets, you can forcefully clear the cache using this button. This will force the app to fetch the library from scratch.",
"reset-cache": "Reset Cache",
"recent-albums": "Recent Albums"
}

View File

@@ -0,0 +1,28 @@
{
"play-next": "Reproducir siguente",
"play-album": "Reproducir Álbum",
"queue": "Cola",
"add-to-queue": "Añadir a la cola",
"clear-queue": "Limpiar cola",
"no-results": "No hay resultados...",
"album": "Álbum",
"albums": "Álbumes",
"all-albums": "Todos los Álbumes",
"search": "Buscar",
"music": "Música",
"now-playing": "Reproduciendo ahora",
"onboarding-welcome": "Bienvenido!",
"onboarding-intro": "Jellyfin Audio Player te permitirá reproducir tu biblioteca musical desde cualquier sitio, con suporte completo para audio en segundo plano y casteo en otros dispositivos.",
"onboarding-cta": "Para empezar necesitas un servidor de Jellyfin. Pulsa el botón de abajo para introducir la dirección del servidor y autentifícate con tus credenciales.",
"set-jellyfin-server": "Introduce servidor de Jellyfin",
"set-jellyfin-server-instruction": "Por favor introduce la URL de tu servidor de Jellyfin. Acuérdate de incluir protocolo y puerto",
"settings": "Configuración",
"jellyfin-library": "Biblioteca Jellyfin",
"jellyfin-server-url": "Url del servidor Jellyfin",
"jellyfin-access-token": "Token de acceso Jellyfin",
"jellyfin-user-id": "ID de usuario Jellyfin",
"setting-cache": "Caché",
"setting-cache-description": "Si has actualizado la biblioteca de Jellyfin pero tu aplicación muestra resultados anteriores, puedes forzar la actualización de la biblioteca pulsando este botón y se volverá a cargar toda información",
"reset-cache": "Resetear Caché",
"recent-albums": "Álbumes recientes"
}

View File

@@ -0,0 +1,28 @@
{
"play-next": "Lire ensuite",
"play-album": "Jouer l'album",
"queue": "File d'attente",
"add-to-queue": "Ajouter à la file d'attente",
"clear-queue": "Vider la file d'attente",
"no-results": "Pas de résultats...",
"album": "Album",
"albums": "Albums",
"all-albums": "Tous les Albums",
"search": "Rechercher",
"music": "Musique",
"now-playing": "En cours",
"onboarding-welcome": "Bienvenue !",
"onboarding-intro": "Jellyfin Audio Player vous permettra de diffuser votre bibliothèque musicale de n'importe où, avec un support de la lecture en arrière plan et la diffusion à distance.",
"onboarding-cta": "Pour utiliser Jellyfin Audio Player, vous avez besoin d'un serveur Jellyfin. Cliquez sur le bouton ci-dessous pour entrer l'adresse de votre serveur Jellyfin et vous y connecter.",
"set-jellyfin-server": "Configurer le serveur Jellyfin",
"set-jellyfin-server-instruction": "Veuillez entrer l'URL de votre serveur Jellyfin. Assurez-vous d'inclure le protocole et le port",
"settings": "Réglages",
"jellyfin-library": "Bibliothèque Jellyfin",
"jellyfin-server-url": "URL du serveur Jellyfin",
"jellyfin-access-token": "Jeton d'accès à la Jellyfin",
"jellyfin-user-id": "ID utilisateur Jellyfin",
"setting-cache": "Cache",
"setting-cache-description": "Si vous avez mis à jour votre bibliothèque Jellyfin mais que l'application conserve toujours des ressources en cache, vous pouvez vider le cache en utilisant ce bouton. Cela forcera l'application à récupérer lintégralité de bibliothèque.",
"reset-cache": "Réinitialiser le cache",
"recent-albums": "Albums récents"
}

View File

@@ -0,0 +1,28 @@
{
"play-next": "Speel volgende",
"play-album": "Speel album",
"queue": "Wachtrij",
"add-to-queue": "Voeg toe aan wachtrij",
"clear-queue": "Wis wachtrij",
"no-results": "Geen resultaten...",
"album": "Album",
"albums": "Albums",
"all-albums": "Alle Albums",
"search": "Zoeken",
"music": "Muziek",
"now-playing": "Nu spelend",
"onboarding-welcome": "Welkom!",
"onboarding-intro": "Jellyfin Audio Player maakt het mogelijk om van waar dan ook je muziek te streamen, met volledige support voor achtegroundaudio en casting.",
"onboarding-cta": "Om te starten moet je een Jellyfin server hebben. Klik de onderstaande knop om het adres van je Jellyfin server in te vullen en er in te loggen.",
"set-jellyfin-server": "Kies Jellyfin Server in",
"set-jellyfin-server-instruction": "Vul alsjeblieft je Jellyfin server URL in. Voeg ook het protocol en de poort toe",
"settings": "Instellingen",
"jellyfin-library": "Jellyfin Bibliotheek",
"jellyfin-server-url": "Jellyfin Server URL",
"jellyfin-access-token": "Jellyfin Toegangstoken",
"jellyfin-user-id": "Jellyfin Gebruiker ID",
"setting-cache": "Cache",
"setting-cache-description": "Als je Jellyfin bibliotheek geüpdatet hebt, maar de app nog aan elementen uit de cache vasthoudt, kun je de cache geforceerd leegmaken met deze knop. Dit forceert de app om de bibliotheek weer vanaf het begin op te bouwen.",
"reset-cache": "Leeg Cache",
"recent-albums": "Recente Albums"
}

26
src/localisation/types.ts Normal file
View File

@@ -0,0 +1,26 @@
export type LocaleKeys = 'play-next'
| 'play-album'
| 'queue'
| 'add-to-queue'
| 'clear-queue'
| 'no-results'
| 'album'
| 'albums'
| 'all-albums'
| 'search'
| 'music'
| 'now-playing'
| 'onboarding-welcome'
| 'onboarding-intro'
| 'onboarding-cta'
| 'set-jellyfin-server'
| 'set-jellyfin-server-instruction'
| 'settings'
| 'jellyfin-library'
| 'jellyfin-server-url'
| 'jellyfin-access-token'
| 'jellyfin-user-id'
| 'setting-cache'
| 'setting-cache-description'
| 'reset-cache'
| 'recent-albums'

View File

@@ -6,6 +6,7 @@ import Album from './stacks/Album';
import RecentAlbums from './stacks/RecentAlbums';
import Search from './stacks/Search';
import { THEME_COLOR } from 'CONSTANTS';
import { t } from '@localisation';
const Stack = createStackNavigator<StackParams>();
@@ -17,10 +18,10 @@ const navigationOptions = {
function MusicStack() {
return (
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={navigationOptions}>
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: 'Recent Albums' }} />
<Stack.Screen name="Albums" component={Albums} />
<Stack.Screen name="Album" component={Album} />
<Stack.Screen name="Search" component={Search} />
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: t('recent-albums') }} />
<Stack.Screen name="Albums" component={Albums} options={{ headerTitle: t('albums') }} />
<Stack.Screen name="Album" component={Album} options={{ headerTitle: t('album') }} />
<Stack.Screen name="Search" component={Search} options={{ headerTitle: t('search') }} />
</Stack.Navigator>
);
}

View File

@@ -1,9 +1,9 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { StackParams } from '../types';
import { Text, ScrollView, Dimensions, Button, RefreshControl } from 'react-native';
import { Text, ScrollView, Dimensions, Button, RefreshControl, StyleSheet } from 'react-native';
import { useGetImage } from 'utility/JellyfinApi';
import styled, { css } from 'styled-components/native';
import { useRoute, RouteProp } from '@react-navigation/native';
import { useRoute, RouteProp, useNavigation } from '@react-navigation/native';
import FastImage from 'react-native-fast-image';
import { useDispatch } from 'react-redux';
import { differenceInDays } from 'date-fns';
@@ -11,14 +11,36 @@ import { useTypedSelector } from 'store';
import { fetchTracksByAlbum } from 'store/music/actions';
import { ALBUM_CACHE_AMOUNT_OF_DAYS, THEME_COLOR } from 'CONSTANTS';
import usePlayAlbum from 'utility/usePlayAlbum';
import usePlayTrack from 'utility/usePlayTrack';
import TouchableHandler from 'components/TouchableHandler';
import useCurrentTrack from 'utility/useCurrentTrack';
import { colors } from 'components/Colors';
import TrackPlayer from 'react-native-track-player';
import { t } from '@localisation';
type Route = RouteProp<StackParams, 'Album'>;
const Screen = Dimensions.get('screen');
const styles = StyleSheet.create({
name: {
...colors.text,
fontSize: 36,
fontWeight: 'bold'
},
artist: {
...colors.text,
fontSize: 24,
opacity: 0.5,
marginBottom: 24
},
index: {
...colors.text,
width: 20,
opacity: 0.5,
marginRight: 5
}
});
const AlbumImage = styled(FastImage)`
border-radius: 10px;
width: ${Screen.width * 0.6}px;
@@ -29,7 +51,6 @@ const AlbumImage = styled(FastImage)`
const TrackContainer = styled.View<{isPlaying: boolean}>`
padding: 15px;
border-bottom-width: 1px;
border-bottom-color: #eee;
flex-direction: row;
${props => props.isPlaying && css`
@@ -51,11 +72,26 @@ const Album: React.FC = () => {
const getImage = useGetImage();
const playAlbum = usePlayAlbum();
const currentTrack = useCurrentTrack();
const navigation = useNavigation();
// Setup callbacks
const selectAlbum = useCallback(() => { playAlbum(id); }, [playAlbum, id]);
const selectTrack = usePlayTrack();
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
const selectTrack = useCallback(async (trackId) => {
const tracks = await playAlbum(id, false);
if (tracks) {
const track = tracks.find((t) => t.id.startsWith(trackId));
if (track) {
await TrackPlayer.skip(track.id);
await TrackPlayer.play();
}
}
}, [playAlbum, id]);
const longPressTrack = useCallback((trackId: string) => {
navigation.navigate('TrackPopupMenu', { trackId });
}, [navigation]);
// Retrieve album tracks on load
useEffect(() => {
@@ -71,22 +107,27 @@ const Album: React.FC = () => {
return (
<ScrollView
style={{ backgroundColor: '#f6f6f6', padding: 20, paddingBottom: 50 }}
contentContainerStyle={{ padding: 20, paddingBottom: 50 }}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
}
>
<AlbumImage source={{ uri: getImage(album?.Id) }} />
<Text style={{ fontSize: 36, fontWeight: 'bold' }} >{album?.Name}</Text>
<Text style={{ fontSize: 24, opacity: 0.5, marginBottom: 24 }}>{album?.AlbumArtist}</Text>
<Button title="Play Album" onPress={selectAlbum} color={THEME_COLOR} />
<AlbumImage source={{ uri: getImage(album?.Id) }} style={colors.imageBackground} />
<Text style={styles.name} >{album?.Name}</Text>
<Text style={styles.artist}>{album?.AlbumArtist}</Text>
<Button title={t('play-album')} onPress={selectAlbum} color={THEME_COLOR} />
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
<TouchableHandler key={trackId} id={trackId} onPress={selectTrack}>
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false}>
<Text style={{ width: 20, opacity: 0.5, marginRight: 5 }}>
<TouchableHandler
key={trackId}
id={trackId}
onPress={selectTrack}
onLongPress={longPressTrack}
>
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={colors.border}>
<Text style={styles.index}>
{tracks[trackId]?.IndexNumber}
</Text>
<Text>{tracks[trackId]?.Name}</Text>
<Text style={colors.text}>{tracks[trackId]?.Name}</Text>
</TrackContainer>
</TouchableHandler>
) : undefined}

View File

@@ -15,6 +15,7 @@ import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
import AlphabetScroller from 'components/AlphabetScroller';
import { EntityId } from '@reduxjs/toolkit';
import styled from 'styled-components/native';
import { colors } from 'components/Colors';
interface VirtualizedItemInfo {
section: SectionedId,
@@ -39,11 +40,10 @@ function generateSection({ section }: { section: SectionedId }) {
}
const SectionContainer = styled.View`
background-color: #f6f6f6;
border-bottom-color: #eee;
border-bottom-width: 1px;
height: 50px;
justify-content: center;
padding: 0 10px;
`;
const SectionText = styled.Text`
@@ -51,13 +51,15 @@ const SectionText = styled.Text`
font-weight: bold;
`;
const sectionStyles = { ...colors.view, ...colors.border };
class SectionHeading extends PureComponent<{ label: string }> {
render() {
const { label } = this.props;
return (
<SectionContainer>
<SectionText>{label}</SectionText>
<SectionContainer style={sectionStyles}>
<SectionText style={colors.text}>{label}</SectionText>
</SectionContainer>
);
}
@@ -82,9 +84,9 @@ class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
return (
<TouchableHandler id={id as string} onPress={onPress}>
<AlbumItem>
<AlbumImage source={{ uri: imageUrl }} />
<Text numberOfLines={1}>{name}</Text>
<HalfOpacity numberOfLines={1}>{artist}</HalfOpacity>
<AlbumImage source={{ uri: imageUrl }} style={colors.imageBackground} />
<Text numberOfLines={1} style={colors.text}>{name}</Text>
<HalfOpacity style={colors.text} numberOfLines={1}>{artist}</HalfOpacity>
</AlbumItem>
</TouchableHandler>
);
@@ -153,7 +155,7 @@ const Albums: React.FC = () => {
const nextItem = section.data[index + 1];
return (
<View style={{ flexDirection: 'row' }} key={item}>
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
<GeneratedAlbumItem
id={item}
imageUrl={getImage(item as string)}
@@ -184,19 +186,17 @@ const Albums: React.FC = () => {
return (
<SafeAreaView>
<ListContainer>
<AlphabetScroller onSelect={selectLetter} />
<SectionList
sections={sections}
refreshing={isLoading}
onRefresh={retrieveData}
getItemLayout={getItemLayout}
ref={listRef}
keyExtractor={(item, index) => `${item}_${index}`}
renderSectionHeader={generateSection}
renderItem={generateItem}
/>
</ListContainer>
<AlphabetScroller onSelect={selectLetter} />
<SectionList
sections={sections}
refreshing={isLoading}
onRefresh={retrieveData}
getItemLayout={getItemLayout}
ref={listRef}
keyExtractor={(item, index) => `${item}_${index}`}
renderSectionHeader={generateSection}
renderItem={generateItem}
/>
</SafeAreaView>
);
};

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect } from 'react';
import { useGetImage } from 'utility/JellyfinApi';
import { Album, NavigationProp } from '../types';
import { Text, SafeAreaView, FlatList } from 'react-native';
import { Text, SafeAreaView, FlatList, StyleSheet } from 'react-native';
import { useDispatch } from 'react-redux';
import { useNavigation } from '@react-navigation/native';
import { useTypedSelector } from 'store';
@@ -12,6 +12,15 @@ import AlbumImage, { AlbumItem } from './components/AlbumImage';
import { useRecentAlbums } from 'store/music/selectors';
import { Header } from 'components/Typography';
import ListButton from 'components/ListButton';
import { colors } from 'components/Colors';
import { t } from '@localisation';
const styles = StyleSheet.create({
artist: {
...colors.text,
opacity: 0.5,
}
});
const NavigationHeader: React.FC = () => {
const navigation = useNavigation();
@@ -20,9 +29,9 @@ const NavigationHeader: React.FC = () => {
return (
<ListContainer>
<ListButton onPress={handleAllAlbumsClick}>All Albums</ListButton>
<ListButton onPress={handleSearchClick}>Search</ListButton>
<Header>Recent Albums</Header>
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
<ListButton onPress={handleSearchClick}>{t('search')}</ListButton>
<Header style={colors.text}>{t('recent-albums')}</Header>
</ListContainer>
);
};
@@ -58,9 +67,9 @@ const RecentAlbums: React.FC = () => {
renderItem={({ item }) => (
<TouchableHandler id={item} onPress={selectAlbum}>
<AlbumItem>
<AlbumImage source={{ uri: getImage(item) }} />
<Text numberOfLines={1}>{albums[item]?.Name}</Text>
<Text numberOfLines={1} style={{ opacity: 0.5 }}>{albums[item]?.AlbumArtist}</Text>
<AlbumImage source={{ uri: getImage(item) }} style={colors.imageBackground} />
<Text style={colors.text} numberOfLines={1}>{albums[item]?.Name}</Text>
<Text style={styles.artist} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
</AlbumItem>
</TouchableHandler>
)}

View File

@@ -11,6 +11,8 @@ import { useNavigation } from '@react-navigation/native';
import { useGetImage } from 'utility/JellyfinApi';
import { NavigationProp } from '../types';
import FastImage from 'react-native-fast-image';
import { colors } from 'components/Colors';
import { t } from '@localisation';
const Container = styled.View`
padding: 0 20px;
@@ -20,7 +22,6 @@ const AlbumImage = styled(FastImage)`
border-radius: 4px;
width: 25px;
height: 25px;
background-color: #fefefe;
margin-right: 10px;
`;
@@ -33,7 +34,6 @@ const HalfOpacity = styled.Text`
const SearchResult = styled.View`
flex-direction: row;
align-items: center;
border-bottom-color: #ddd;
border-bottom-width: 1px;
margin-left: 15px;
padding-right: 15px;
@@ -51,7 +51,7 @@ export default function Search() {
const [searchTerm, setSearchTerm] = useState('');
const albums = useTypedSelector(state => state.music.albums.entities);
const [results, setResults] = useState<Fuse.FuseResult<Album>[]>([]);
let fuse = useRef<Fuse<Album, typeof fuseOptions>>();
const fuse = useRef<Fuse<Album, typeof fuseOptions>>();
// Prepare helpers
const navigation = useNavigation<NavigationProp>();
@@ -89,8 +89,8 @@ export default function Search() {
const HeaderComponent = React.useMemo(() => (
<Container>
<Input value={searchTerm} onChangeText={setSearchTerm} placeholder="Search..." />
{(searchTerm.length && !results.length) ? <Text>No results...</Text> : null}
<Input value={searchTerm} onChangeText={setSearchTerm} style={colors.input} placeholder={t('search') + '...'} />
{(searchTerm.length && !results.length) ? <Text>{t('no-results')}</Text> : null}
</Container>
), [searchTerm, results, setSearchTerm]);
@@ -105,13 +105,13 @@ export default function Search() {
data={results}
renderItem={({ item: { item: album } }) =>(
<TouchableHandler id={album.Id} onPress={selectAlbum}>
<SearchResult>
<SearchResult style={colors.border}>
<AlbumImage source={{ uri: getImage(album.Id) }} />
<View>
<Text numberOfLines={1} ellipsizeMode="tail">
<Text numberOfLines={1} ellipsizeMode="tail" style={colors.text}>
{album.Name} - {album.AlbumArtist}
</Text>
<HalfOpacity>Album</HalfOpacity>
<HalfOpacity style={colors.text}>{t('album')}</HalfOpacity>
</View>
</SearchResult>
</TouchableHandler>

View File

@@ -14,7 +14,6 @@ const AlbumImage = styled(FastImage)`
border-radius: 10px;
width: ${Screen.width / 2 - 40}px;
height: ${Screen.width / 2 - 40}px;
background-color: #fefefe;
margin-bottom: 5px;
`;

View File

@@ -2,7 +2,6 @@ import styled from 'styled-components/native';
const ListContainer = styled.View`
padding: 10px;
background-color: #f6f6f6;
`;
export default ListContainer;

View File

@@ -0,0 +1,77 @@
import React, { useCallback, useEffect } from 'react';
import styled from 'styled-components/native';
import { THEME_COLOR } from 'CONSTANTS';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from 'screens';
import { useTypedSelector } from 'store';
import { useDispatch } from 'react-redux';
import { setOnboardingStatus } from 'store/settings/actions';
import { t } from '@localisation';
const Container = styled.SafeAreaView`
background-color: ${THEME_COLOR};
flex: 1;
justify-content: center;
`;
const TextContainer = styled.ScrollView`
padding: 25px;
`;
const Text = styled.Text`
text-align: center;
color: white;
margin-bottom: 10px;
`;
const ButtonContainer = styled.View`
margin-top: 50px;
`;
const Logo = styled.Image`
width: 150px;
height: 150px;
margin: 0 auto 50px auto;
`;
function Onboarding() {
// Get account from Redux and dispatcher
const account = useTypedSelector(state => state.settings.jellyfin);
const dispatch = useDispatch();
// Also retrieve the navigation handler so that we can open the modal in
// which the Jellyfin server is set
const navigation = useNavigation<NavigationProp>();
const handleClick = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
// We'll also respond to any change in the account, setting the onboarding
// status to true, so that the app becomes available.
useEffect(() => {
if (account) {
dispatch(setOnboardingStatus(true));
}
}, [account, dispatch]);
return (
<Container>
<TextContainer contentContainerStyle={{ flexGrow: 1, justifyContent: 'center' }}>
<Logo source={require('../../assets/app-icon-white.png')} />
<Text >
{t('onboarding-welcome')}
</Text>
<Text>
{t('onboarding-intro')}
</Text>
<Text>
{t('onboarding-cta')}
</Text>
<ButtonContainer>
<Button title={t('set-jellyfin-server')} color="#ffffff" onPress={handleClick} />
</ButtonContainer>
</TextContainer>
</Container>
);
}
export default Onboarding;

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import TrackPlayer, { usePlaybackState, STATE_PLAYING, STATE_PAUSED } from 'react-native-track-player';
import { TouchableOpacity } from 'react-native';
import styled from 'styled-components/native';
@@ -7,8 +7,13 @@ import ForwardIcon from 'assets/forwards.svg';
import BackwardIcon from 'assets/backwards.svg';
import PlayIcon from 'assets/play.svg';
import PauseIcon from 'assets/pause.svg';
import RepeatIcon from 'assets/repeat.svg';
// import ShuffleIcon from 'assets/shuffle.svg';
import { useColorScheme } from 'react-native-appearance';
import { THEME_COLOR } from 'CONSTANTS';
const BUTTON_SIZE = 40;
const BUTTON_SIZE_SMALL = 25;
const pause = () => TrackPlayer.pause();
const play = () => TrackPlayer.play();
@@ -32,61 +37,133 @@ const Button = styled.View`
`;
export default function MediaControls() {
const scheme = useColorScheme();
const fill = scheme === 'dark' ? '#ffffff' : '#000000';
return (
<Container>
<Buttons>
<Button>
<PreviousButton />
<PreviousButton fill={fill} />
</Button>
<MainButton />
<MainButton fill={fill} />
<Button>
<NextButton />
<NextButton fill={fill} />
</Button>
</Buttons>
<Buttons>
<Button>
<RepeatButton fill={fill} />
</Button>
<Button>
{/* <ShuffleButton fill={fill} /> */}
</Button>
</Buttons>
</Container>
);
}
export function PreviousButton() {
export function PreviousButton({ fill }: { fill: string }) {
const hasQueue = useHasQueue();
return (
<TouchableOpacity onPress={previous} disabled={!hasQueue} style={{ opacity: hasQueue ? 1 : 0.5 }}>
<BackwardIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill="#000" />
<BackwardIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill={fill} />
</TouchableOpacity>
);
}
export function NextButton() {
export function NextButton({ fill }: { fill: string }) {
const hasQueue = useHasQueue();
return (
<TouchableOpacity onPress={next} disabled={!hasQueue} style={{ opacity: hasQueue ? 1 : 0.5 }}>
<ForwardIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill="#000" />
<ForwardIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill={fill} />
</TouchableOpacity>
);
}
export function MainButton() {
export function RepeatButton({ fill }: { fill: string}) {
const [isRepeating, setRepeating] = useState(false);
const handlePress = useCallback(() => setRepeating(!isRepeating), [isRepeating, setRepeating]);
const listener = useRef<TrackPlayer.EmitterSubscription | null>(null);
// The callback that should determine whether we need to repeeat or not
const handleEndEvent = useCallback(async () => {
if (isRepeating) {
// Retrieve all current tracks
const tracks = await TrackPlayer.getQueue();
// Then skip to the first track
await TrackPlayer.skip(tracks[0].id);
// Cautiously reset the seek time, as there might only be a single
// item in queue.
await TrackPlayer.seekTo(0);
// Then play the item
await TrackPlayer.play();
}
}, [isRepeating]);
// Subscribe to ended event handler so that we can restart the queue from
// the start if looping is enabled
useEffect(() => {
// Set the event listener
listener.current = TrackPlayer.addEventListener('playback-queue-ended', handleEndEvent);
// Then clean up after
return function cleanup() {
listener?.current?.remove();
};
}, [handleEndEvent]);
return (
<TouchableOpacity onPress={handlePress} style={{ opacity: isRepeating ? 1 : 0.5 }}>
<RepeatIcon
width={BUTTON_SIZE_SMALL}
height={BUTTON_SIZE_SMALL}
fill={isRepeating ? THEME_COLOR : fill}
/>
</TouchableOpacity>
);
}
// export function ShuffleButton({ fill }: { fill: string}) {
// const [isShuffling, setShuffling] = useState(false);
// const handlePress = useCallback(() => setShuffling(!isShuffling), [isShuffling, setShuffling]);
// return (
// <TouchableOpacity onPress={handlePress} style={{ opacity: isShuffling ? 1 : 0.5 }}>
// <ShuffleIcon
// width={BUTTON_SIZE_SMALL}
// height={BUTTON_SIZE_SMALL}
// fill={isShuffling ? THEME_COLOR : fill}
// />
// </TouchableOpacity>
// );
// }
export function MainButton({ fill }: { fill: string }) {
const state = usePlaybackState();
switch (state) {
case STATE_PLAYING:
return (
<TouchableOpacity onPress={pause}>
<PauseIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill="#000" />
<PauseIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill={fill} />
</TouchableOpacity>
);
case STATE_PAUSED:
return (
<TouchableOpacity onPress={play}>
<PlayIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill="#000" />
<PlayIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill={fill} />
</TouchableOpacity>
);
default:
return (
<TouchableOpacity onPress={pause} disabled>
<PauseIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill="#000" />
<PauseIcon width={BUTTON_SIZE} height={BUTTON_SIZE} fill={fill} />
</TouchableOpacity>
);
}

View File

@@ -1,29 +1,51 @@
import React from 'react';
import { Text, Dimensions, View } from 'react-native';
import { Text, Dimensions, View, StyleSheet } from 'react-native';
import useCurrentTrack from 'utility/useCurrentTrack';
import styled from 'styled-components/native';
import FastImage from 'react-native-fast-image';
import { colors } from 'components/Colors';
const Screen = Dimensions.get('screen');
const Artwork = styled(FastImage)`
border-radius: 10px;
background-color: #fbfbfb;
width: ${Screen.width * 0.8}px;
height: ${Screen.width * 0.8}px;
margin: 25px auto;
display: flex;
`;
const styles = StyleSheet.create({
artist: {
...colors.text,
fontWeight: 'bold',
fontSize: 24,
marginBottom: 12,
},
title: {
...colors.text,
fontSize: 18,
marginBottom: 12,
textAlign: 'center',
paddingLeft: 20,
paddingRight: 20,
}
});
export default function NowPlaying() {
const track = useCurrentTrack();
return (
<View style={{ alignItems: 'center' }}>
<Artwork style={{ flex: 1 }} source={{ uri: track?.artwork }} />
<Text style={{ fontWeight: 'bold', fontSize: 24, marginBottom: 12 }} >{track?.artist}</Text>
<Text style={{ fontSize: 18, marginBottom: 12, textAlign: 'center', paddingLeft: 20, paddingRight: 20 }}>{track?.title}</Text>
<Artwork
style={colors.imageBackground}
source={{
uri: track?.artwork,
priority: FastImage.priority.high,
}}
/>
<Text style={styles.artist} >{track?.artist}</Text>
<Text style={styles.title}>{track?.title}</Text>
</View>
);
}

View File

@@ -1,9 +1,10 @@
import React, { Component } from 'react';
import TrackPlayer from 'react-native-track-player';
import styled from 'styled-components/native';
import { Text } from 'react-native';
import { Text, Platform } from 'react-native';
import Slider from '@react-native-community/slider';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from 'components/Colors';
const NumberBar = styled.View`
flex-direction: row;
@@ -74,11 +75,12 @@ export default class ProgressBar extends Component<{}, State> {
onValueChange={this.handleGesture}
onSlidingComplete={this.handleEndOfGesture}
minimumTrackTintColor={THEME_COLOR}
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
disabled={!duration}
/>
<NumberBar>
<Text>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
<Text>{getMinutes(duration)}:{getSeconds(duration)}</Text>
<Text style={colors.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
<Text style={colors.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
</NumberBar>
</>
);

View File

@@ -1,29 +1,44 @@
import React, { useCallback } from 'react';
import useQueue from 'utility/useQueue';
import { View, Text } from 'react-native';
import { View, Text, StyleSheet, Button } from 'react-native';
import styled, { css } from 'styled-components/native';
import useCurrentTrack from 'utility/useCurrentTrack';
import TouchableHandler from 'components/TouchableHandler';
import TrackPlayer from 'react-native-track-player';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from 'components/Colors';
import { t } from '@localisation';
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean }>`
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
padding: 10px;
border-bottom-width: 1px;
border-bottom-color: #eee;
${props => props.active && css`
font-weight: 900;
background-color: ${THEME_COLOR}16;
padding: 20px 35px;
margin: 0 -25px;
`}
${props => props.alreadyPlayed && css`
opacity: 0.25;
opacity: 0.5;
`}
`;
const ClearQueue = styled.View`
margin: 20px 0;
`;
const styles = StyleSheet.create({
title: {
...colors.text,
marginBottom: 2,
},
artist: {
...colors.text,
opacity: 0.5,
}
});
export default function Queue() {
const queue = useQueue();
const currentTrack = useCurrentTrack();
@@ -32,18 +47,32 @@ export default function Queue() {
await TrackPlayer.skip(trackId);
await TrackPlayer.play();
}, []);
const clearQueue = useCallback(async () => {
await TrackPlayer.reset();
}, []);
return (
<View>
<Text style={{ marginTop: 20, marginBottom: 20 }}>Queue</Text>
<Text style={{ marginTop: 20, marginBottom: 20 }}>{t('queue')}</Text>
{queue.map((track, i) => (
<TouchableHandler id={track.id} onPress={playTrack} key={i}>
<QueueItem active={currentTrack?.id === track.id} key={i} alreadyPlayed={i < currentIndex}>
<Text style={{marginBottom: 2}}>{track.title}</Text>
<Text style={{ opacity: 0.5 }}>{track.artist}</Text>
<QueueItem
active={currentTrack?.id === track.id}
key={i}
alreadyPlayed={i < currentIndex}
style={{
...colors.border,
...currentTrack?.id === track.id ? colors.activeBackground : {},
}}
>
<Text style={styles.title}>{track.title}</Text>
<Text style={styles.artist}>{track.artist}</Text>
</QueueItem>
</TouchableHandler>
))}
<ClearQueue>
<Button title={t('clear-queue')} color={THEME_COLOR} onPress={clearQueue} />
</ClearQueue>
</View>
);
}

View File

@@ -1,22 +1,25 @@
import React from 'react';
import { StyleSheet, ScrollView } from 'react-native';
import MediaControls from './components/MediaControls';
import ProgressBar from './components/ProgressBar';
import NowPlaying from './components/NowPlaying';
import styled from 'styled-components/native';
import Queue from './components/Queue';
import { colors } from 'components/Colors';
const Container = styled.ScrollView`
background-color: #fff;
padding: 25px;
`;
const styles = StyleSheet.create({
outer: colors.view,
inner: {
padding: 25,
}
});
export default function Player() {
return (
<Container>
<ScrollView contentContainerStyle={styles.inner} style={styles.outer}>
<NowPlaying />
<MediaControls />
<ProgressBar />
<Queue />
</Container>
</ScrollView>
);
}

View File

@@ -0,0 +1,27 @@
import React, { useCallback } from 'react';
import TrackPlayer from 'react-native-track-player';
import { SubHeader } from 'components/Typography';
import { Text, Button } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
import { useDispatch } from 'react-redux';
import music from 'store/music';
import { t } from '@localisation';
export default function CacheSettings() {
const dispatch = useDispatch();
const handleClearCache = useCallback(() => {
// Dispatch an action to reset the music subreducer state
dispatch(music.actions.reset());
// Also clear the TrackPlayer queue
TrackPlayer.reset();
}, [dispatch]);
return (
<>
<SubHeader>{t('setting-cache')}</SubHeader>
<Text>{t('setting-cache-description')}</Text>
<Button title={t('reset-cache')} onPress={handleClearCache} color={THEME_COLOR} />
</>
);
}

View File

@@ -0,0 +1,45 @@
import styled from 'styled-components/native';
import { useNavigation } from '@react-navigation/native';
import React, { useCallback } from 'react';
import { Text, Button } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
import { SubHeader } from 'components/Typography';
import { colors } from 'components/Colors';
import { NavigationProp } from '../..';
import { useTypedSelector } from 'store';
import { t } from '@localisation';
const InputContainer = styled.View`
margin: 10px 0;
`;
const Input = styled.TextInput`
padding: 15px;
margin-top: 5px;
border-radius: 5px;
`;
export default function LibrarySettings() {
const { jellyfin } = useTypedSelector(state => state.settings);
const navigation = useNavigation<NavigationProp>();
const handleSetLibrary = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
return (
<>
<SubHeader>{t('jellyfin-library')}</SubHeader>
<InputContainer>
<Text style={colors.text}>{t('jellyfin-server-url')}</Text>
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={colors.input} />
</InputContainer>
<InputContainer>
<Text style={colors.text}>{t('jellyfin-access-token')}</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={colors.input} />
</InputContainer>
<InputContainer>
<Text style={colors.text}>{t('jellyfin-user-id')}</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={colors.input} />
</InputContainer>
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} color={THEME_COLOR} />
</>
);
}

View File

@@ -1,54 +1,19 @@
import React, { useCallback } from 'react';
import { View, Text, SafeAreaView, Button } from 'react-native';
import { Picker } from '@react-native-community/picker';
import { ScrollView } from 'react-native-gesture-handler';
import styled from 'styled-components/native';
import { useSelector } from 'react-redux';
import { AppState } from 'store';
import { useNavigation } from '@react-navigation/native';
import { NavigationProp } from '..';
import { THEME_COLOR } from 'CONSTANTS';
const InputContainer = styled.View`
margin: 10px 0;
`;
const Input = styled.TextInput`
background-color: #fbfbfb;
padding: 15px;
margin-top: 5px;
border-radius: 5px;
`;
import React from 'react';
import { View, SafeAreaView, ScrollView } from 'react-native';
import { Header } from 'components/Typography';
import { colors } from 'components/Colors';
import Library from './components/Library';
import Cache from './components/Cache';
import { t } from '@localisation';
export default function Settings() {
const { jellyfin, bitrate } = useSelector((state: AppState) => state.settings);
const navigation = useNavigation<NavigationProp>();
const handleClick = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
return (
<ScrollView>
<SafeAreaView>
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 36, marginBottom: 24, fontWeight: 'bold' }}>Settings</Text>
<InputContainer>
<Text>Jellyfin Server URL</Text>
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} />
</InputContainer>
<InputContainer>
<Text>Jellyfin Access Token</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} />
</InputContainer>
<InputContainer>
<Text>Jellyfin User ID</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} />
</InputContainer>
<Button title="Set Jellyfin server" onPress={handleClick} color={THEME_COLOR} />
<InputContainer>
<Text>Bitrate</Text>
<Picker selectedValue={bitrate}>
<Picker.Item label="320kbps" value={140000000} />
</Picker>
</InputContainer>
<Header style={colors.text}>{t('settings')}</Header>
<Library />
<Cache />
</View>
</SafeAreaView>
</ScrollView>

View File

@@ -10,8 +10,13 @@ import PlayPauseIcon from 'assets/play-pause-fill.svg';
import NotesIcon from 'assets/notes.svg';
import GearIcon from 'assets/gear.svg';
import { THEME_COLOR } from 'CONSTANTS';
import { useTypedSelector } from 'store';
import Onboarding from './Onboarding';
import TrackPopupMenu from './modals/TrackPopupMenu';
import { ModalStackParams } from './types';
import { t } from '@localisation';
const Stack = createStackNavigator();
const Stack = createStackNavigator<ModalStackParams>();
const Tab = createBottomTabNavigator();
type Screens = {
@@ -34,6 +39,14 @@ function getIcon(route: string): React.FC<any> | null {
}
function Screens() {
const isOnboardingComplete = useTypedSelector(state => state.settings.isOnboardingComplete);
// GUARD: If onboarding has not been completed, we instead render the
// onboarding component, so that the user can get setup in the app.
if (!isOnboardingComplete) {
return <Onboarding />;
}
return (
<Tab.Navigator
screenOptions={({ route }) => ({
@@ -52,9 +65,9 @@ function Screens() {
inactiveTintColor: 'gray',
}}
>
<Tab.Screen name="NowPlaying" component={Player} options={{ tabBarLabel: 'Now Playing' }} />
<Tab.Screen name="Music" component={Music} />
<Tab.Screen name="Settings" component={Settings} />
<Tab.Screen name="NowPlaying" component={Player} options={{ tabBarLabel: t('now-playing') }} />
<Tab.Screen name="Music" component={Music} options={{ tabBarLabel: t('music') }} />
<Tab.Screen name="Settings" component={Settings} options={{ tabBarLabel: t('settings') }} />
</Tab.Navigator>
);
}
@@ -74,6 +87,7 @@ export default function Routes() {
}}>
<Stack.Screen name="Screens" component={Screens} />
<Stack.Screen name="SetJellyfinServer" component={SetJellyfinServer} />
<Stack.Screen name="TrackPopupMenu" component={TrackPopupMenu} />
</Stack.Navigator>
);
}

View File

@@ -7,6 +7,8 @@ import { useDispatch } from 'react-redux';
import { useNavigation, StackActions } from '@react-navigation/native';
import CredentialGenerator from './components/CredentialGenerator';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from 'components/Colors';
import { t } from '@localisation';
export default function SetJellyfinServer() {
// State for first screen
@@ -31,8 +33,10 @@ export default function SetJellyfinServer() {
onCredentialsRetrieved={saveCredentials}
/>
) : (
<View style={{ padding: 20}}>
<Text>Please enter your Jellyfin server URL first. Make sure to include the protocol and port</Text>
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={colors.text}>
{t('set-jellyfin-server-instruction')}
</Text>
<Input
placeholder="https://jellyfin.yourserver.io/"
onChangeText={setServerUrl}
@@ -40,9 +44,10 @@ export default function SetJellyfinServer() {
keyboardType="url"
autoCapitalize="none"
autoCorrect={false}
style={{ ...colors.input, width: '100%' }}
/>
<Button
title="Set server"
title={t('set-jellyfin-server')}
onPress={() => setIsLogginIn(true)}
disabled={!serverUrl?.length}
color={THEME_COLOR}

View File

@@ -0,0 +1,60 @@
import React, { useCallback } from 'react';
import Modal from 'components/Modal';
import { Text, Button } from 'react-native';
import { useNavigation, StackActions, useRoute, RouteProp } from '@react-navigation/native';
import { ModalStackParams } from 'screens/types';
import { useTypedSelector } from 'store';
import { THEME_COLOR } from 'CONSTANTS';
import { SubHeader } from 'components/Typography';
import styled from 'styled-components/native';
import usePlayTrack from 'utility/usePlayTrack';
import { t } from '@localisation';
type Route = RouteProp<ModalStackParams, 'TrackPopupMenu'>;
const Container = styled.View`
padding: 20px;
`;
const Buttons = styled.View`
margin-top: 20px;
flex-direction: row;
justify-content: space-around;
`;
function TrackPopupMenu() {
// Retrieve helpers
const { params: { trackId } } = useRoute<Route>();
const navigation = useNavigation();
const track = useTypedSelector((state) => state.music.tracks.entities[trackId]);
const playTrack = usePlayTrack();
// Set callback to close the modal
const closeModal = useCallback(() => {
navigation.dispatch(StackActions.popToTop());
}, [navigation]);
const handlePlayNext = useCallback(() => {
playTrack(trackId, false, false);
closeModal();
}, [playTrack, closeModal, trackId]);
const handleAddToQueue = useCallback(() => {
playTrack(trackId, false, true);
closeModal();
}, [playTrack, closeModal, trackId]);
return (
<Modal fullSize={false}>
<Container>
<SubHeader>{track?.Name}</SubHeader>
<Text>{track?.Album} - {track?.AlbumArtist}</Text>
<Buttons>
<Button title={t('play-next')} color={THEME_COLOR} onPress={handlePlayNext} />
<Button title={t('add-to-queue')} color={THEME_COLOR} onPress={handleAddToQueue} />
</Buttons>
</Container>
</Modal>
);
}
export default TrackPopupMenu;

View File

@@ -0,0 +1,4 @@
export interface ModalStackParams {
SetJellyfinServer: undefined;
TrackPopupMenu: { trackId: string };
}

View File

@@ -1,19 +1,23 @@
import { configureStore, getDefaultMiddleware, combineReducers } from '@reduxjs/toolkit';
import { useSelector, TypedUseSelectorHook } from 'react-redux';
import AsyncStorage from '@react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
import { persistStore, persistReducer, PersistConfig } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/es/stateReconciler/autoMergeLevel2';
// import logger from 'redux-logger';
const persistConfig = {
const persistConfig: PersistConfig<AppState> = {
key: 'root',
storage: AsyncStorage,
stateReconciler: autoMergeLevel2
};
import settings from './settings';
import music from './music';
import player from './player';
const reducers = combineReducers({
settings,
player: player.reducer,
music: music.reducer,
});
@@ -26,7 +30,7 @@ const store = configureStore({
),
});
export type AppState = ReturnType<typeof store.getState>;
export type AppState = ReturnType<typeof reducers>;
export type AppDispatch = typeof store.dispatch;
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;

View File

@@ -31,7 +31,9 @@ const initialState: State = {
const music = createSlice({
name: 'music',
initialState,
reducers: {},
reducers: {
reset: () => initialState,
},
extraReducers: builder => {
/**
* Fetch All albums

11
src/store/player/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import { createSlice } from '@reduxjs/toolkit';
const player = createSlice({
name: 'player',
initialState: 0,
reducers: {
addNewTrackToPlayer: (state) => state + 1,
}
});
export default player;

View File

@@ -1,4 +1,5 @@
import { createAction } from '@reduxjs/toolkit';
export const setJellyfinCredentials = createAction<{ access_token: string, user_id: string, uri: string, deviced_id: string; }>('SET_JELLYFIN_CREDENTIALS');
export const setBitrate = createAction<number>('SET_BITRATE');
export const setBitrate = createAction<number>('SET_BITRATE');
export const setOnboardingStatus = createAction<boolean>('SET_ONBOARDING_STATUS');

View File

@@ -1,5 +1,5 @@
import { createReducer } from '@reduxjs/toolkit';
import { setBitrate, setJellyfinCredentials } from './actions';
import { setBitrate, setJellyfinCredentials, setOnboardingStatus } from './actions';
interface State {
jellyfin?: {
@@ -9,10 +9,12 @@ interface State {
device_id: string;
}
bitrate: number;
isOnboardingComplete: boolean;
}
const initialState: State = {
bitrate: 140000000
bitrate: 140000000,
isOnboardingComplete: false,
};
const settings = createReducer(initialState, {
@@ -24,6 +26,10 @@ const settings = createReducer(initialState, {
...state,
bitrate: action.payload,
}),
[setOnboardingStatus.type]: (state, action) => ({
...state,
isOnboardingComplete: action.payload,
})
});
export default settings;

View File

@@ -15,6 +15,8 @@ function generateConfig(credentials: Credentials): RequestInit {
const baseTrackOptions: Record<string, string> = {
// Not sure where this number refers to, but setting it to 140000000 appears
// to do wonders for making stuff work
// NOTE: Apparently setting a bitrate is as of yet unsupported in the
// Jellyfin core, and hence this value is not used
MaxStreamingBitrate: '140000000',
MaxSampleRate: '48000',
// This must be set to support client seeking
@@ -24,7 +26,7 @@ const baseTrackOptions: Record<string, string> = {
// react-native-track-player. This is set to be merged and released very
// soon: https://github.com/react-native-kit/react-native-track-player/pull/950
// Container: 'mp3',
Container: 'mp3,aac,m4a,m4b|aac,alac,m4a,m4b|alac',
Container: 'mp3,aac,m4a,m4b|aac,alac,m4a,m4b|alac,flac',
AudioCodec: 'aac',
static: 'true',
};
@@ -43,7 +45,7 @@ export function generateTrack(track: AlbumTrack, credentials: Credentials): Trac
DeviceId: credentials?.device_id || '',
};
const trackParams = new URLSearchParams(trackOptions).toString();
const url = encodeURI(`${credentials?.uri}/Audio/${track.Id}/universal.mp3?${trackParams}`);
const url = encodeURI(`${credentials?.uri}/Audio/${track.Id}/universal?${trackParams}`);
return {
id: track.Id,

View File

@@ -14,15 +14,22 @@ export default function useCurrentTrack(): Track | undefined {
// GUARD: Only fetch current track if there is a current track
if (!currentTrackId) {
setTrack(undefined);
}
// GUARD: Only retrieve new track if it is different from the one we
// have currently in state.
if (currentTrackId === track?.id){
return;
}
// If it is different, retrieve the track and save it
const currentTrack = await TrackPlayer.getTrack(currentTrackId);
setTrack(currentTrack);
};
fetchTrack();
}, [state]);
}, [state, track, setTrack]);
return track;
}

View File

@@ -2,17 +2,22 @@ import { useTypedSelector } from 'store';
import { useCallback } from 'react';
import TrackPlayer, { Track } from 'react-native-track-player';
import { generateTrack } from './JellyfinApi';
import useQueue from './useQueue';
import player from 'store/player';
import { useDispatch } from 'react-redux';
/**
* Generate a callback function that starts playing a full album given its
* supplied id.
*/
export default function usePlayAlbum() {
const dispatch = useDispatch();
const credentials = useTypedSelector(state => state.settings.jellyfin);
const albums = useTypedSelector(state => state.music.albums.entities);
const tracks = useTypedSelector(state => state.music.tracks.entities);
const queue = useQueue();
return useCallback(async function playAlbum(albumId: string) {
return useCallback(async function playAlbum(albumId: string, play = true): Promise<TrackPlayer.Track[] | undefined> {
const album = albums[albumId];
const trackIds = album?.Tracks;
@@ -21,6 +26,29 @@ export default function usePlayAlbum() {
return;
}
// Check if the queue already contains the consecutive track listing
// that is described as part of the album
const queuedAlbum = queue.reduce<TrackPlayer.Track[]>((sum, track) => {
if (track.id.startsWith(trackIds[sum.length])) {
sum.push(track);
} else {
sum = [];
}
return sum;
}, []);
// If the entire album is already in the queue, we can just return those
// tracks, rather than adding it to the queue again.
if (queuedAlbum.length === trackIds.length) {
if (play) {
await TrackPlayer.skip(trackIds[0]);
await TrackPlayer.play();
}
return queuedAlbum;
}
// Convert all trackIds to the relevant format for react-native-track-player
const newTracks = trackIds.map((trackId) => {
const track = tracks[trackId];
@@ -34,7 +62,15 @@ export default function usePlayAlbum() {
// Clear the queue and add all tracks
await TrackPlayer.removeUpcomingTracks();
await TrackPlayer.add(newTracks);
await TrackPlayer.skip(trackIds[0]);
TrackPlayer.play();
}, [credentials, albums, tracks]);
// Then, we'll dispatch the added track event
dispatch(player.actions.addNewTrackToPlayer());
if (play) {
await TrackPlayer.skip(trackIds[0]);
await TrackPlayer.play();
}
return newTracks;
}, [credentials, albums, tracks, queue, dispatch]);
}

View File

@@ -3,17 +3,20 @@ import TrackPlayer from 'react-native-track-player';
import { useTypedSelector } from 'store';
import { generateTrack } from './JellyfinApi';
import useQueue from './useQueue';
import { useDispatch } from 'react-redux';
import player from 'store/player';
/**
* A hook that generates a callback that can setup and start playing a
* particular trackId in the player.
*/
export default function usePlayTrack() {
const dispatch = useDispatch();
const credentials = useTypedSelector(state => state.settings.jellyfin);
const tracks = useTypedSelector(state => state.music.tracks.entities);
const queue = useQueue();
return useCallback(async function playTrack(trackId: string) {
return useCallback(async function playTrack(trackId: string, play = true, addToEnd = true) {
// Get the relevant track
const track = tracks[trackId];
@@ -30,10 +33,35 @@ export default function usePlayTrack() {
...(trackInstances.length ? trackInstances[0] : generateTrack(track, credentials)),
id: `${trackId}_${trackInstances.length}`
};
await TrackPlayer.add([ newTrack ]);
// Then, we'll need to check where to add the track
if (addToEnd) {
await TrackPlayer.add([ newTrack ]);
} else {
// Try and locate the current track
const currentTrackId = await TrackPlayer.getCurrentTrack();
const currentTrackIndex = queue.findIndex(track => track.id === currentTrackId);
// Since the argument is the id to insert the track BEFORE, we need
// to get the current track + 1
const targetTrack = currentTrackIndex >= 0 && queue.length > 1
? queue[currentTrackIndex + 1].id
: undefined;
// Depending on whether this track exists, we either add it there,
// or at the end of the queue.
await TrackPlayer.add([ newTrack ], targetTrack);
}
// Then, we'll dispatch the added track event
dispatch(player.actions.addNewTrackToPlayer());
// Then we'll skip to it and play it
await TrackPlayer.skip(newTrack.id);
TrackPlayer.play();
}, [credentials, tracks, queue]);
if (play) {
await TrackPlayer.skip(newTrack.id);
await TrackPlayer.play();
}
return newTrack;
}, [credentials, tracks, queue, dispatch]);
}

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import TrackPlayer, { usePlaybackState, Track } from 'react-native-track-player';
import { useTypedSelector } from 'store';
/**
* This hook retrieves the current playing track from TrackPlayer
@@ -7,10 +8,11 @@ import TrackPlayer, { usePlaybackState, Track } from 'react-native-track-player'
export default function useQueue(): Track[] {
const state = usePlaybackState();
const [queue, setQueue] = useState<Track[]>([]);
const addedTrackCount = useTypedSelector(state => state.player);
useEffect(() => {
TrackPlayer.getQueue().then(setQueue);
}, [state]);
}, [state, addedTrackCount]);
return queue;
}

View File

@@ -38,7 +38,9 @@
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"paths": {
"@localisation": ["./localisation"]
}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */