Compare commits
59 Commits
v0.0.1-alp
...
v0.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ba83b8efc | ||
|
|
e4daccfe5e | ||
|
|
76c45623a0 | ||
|
|
e4b93ec8c8 | ||
|
|
4635266273 | ||
|
|
8dc287e56a | ||
|
|
6cfa8f7624 | ||
|
|
6cf5b8167b | ||
|
|
dc76ea27d3 | ||
|
|
7adc96ba12 | ||
|
|
42eb7a169b | ||
|
|
3eab7793f5 | ||
|
|
66eb4bd61d | ||
|
|
3312e99911 | ||
|
|
3ae967e137 | ||
|
|
d18daf027e | ||
|
|
0ae7e3ba0c | ||
|
|
e918f089e2 | ||
|
|
491f019fb3 | ||
|
|
0af730dabe | ||
|
|
fadfabc868 | ||
|
|
de73fe6358 | ||
|
|
6f3ed9c988 | ||
|
|
ff484cebed | ||
|
|
1e14ed3271 | ||
|
|
954fe38531 | ||
|
|
83cf0e4d56 | ||
|
|
3b98eb4bf1 | ||
|
|
49bb4dfaab | ||
|
|
9f9e61017e | ||
|
|
65c7db9911 | ||
|
|
481a411e4c | ||
|
|
8eeddebad7 | ||
|
|
974c73e70a | ||
|
|
0988e10a09 | ||
|
|
9ea0753912 | ||
|
|
6cf421f24f | ||
|
|
0df9d4a621 | ||
|
|
6a1d75c27c | ||
|
|
c88158cf16 | ||
|
|
3026fdf4da | ||
|
|
12b53eca4a | ||
|
|
3ff971a773 | ||
|
|
8fac21f4d8 | ||
|
|
6e85013b5a | ||
|
|
edce0329cf | ||
|
|
be40d8a404 | ||
|
|
9138946ba9 | ||
|
|
ea11a7d317 | ||
|
|
91344300c8 | ||
|
|
52146a6e12 | ||
|
|
0712e6b8ca | ||
|
|
2155320f6b | ||
|
|
4ff1e173ba | ||
|
|
c1f6a2984a | ||
|
|
8ba3080734 | ||
|
|
fe22661036 | ||
|
|
3762d9166b | ||
|
|
48dd8c23aa |
15
.eslintrc.js
@@ -8,7 +8,7 @@ module.exports = {
|
|||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
// "plugin:@typescript-eslint/recommended"
|
// 'plugin:@typescript-eslint/recommended'
|
||||||
],
|
],
|
||||||
globals: {
|
globals: {
|
||||||
Atomics: 'readonly',
|
Atomics: 'readonly',
|
||||||
@@ -28,14 +28,15 @@ module.exports = {
|
|||||||
'react-hooks'
|
'react-hooks'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
indent: [
|
indent: 'off',
|
||||||
|
'@typescript-eslint/indent': [
|
||||||
'error',
|
'error',
|
||||||
4,
|
4,
|
||||||
{
|
{
|
||||||
SwitchCase: 1,
|
SwitchCase: 1,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"linebreak-style": [
|
'linebreak-style': [
|
||||||
'error',
|
'error',
|
||||||
'unix'
|
'unix'
|
||||||
],
|
],
|
||||||
@@ -47,10 +48,10 @@ module.exports = {
|
|||||||
'error',
|
'error',
|
||||||
'always'
|
'always'
|
||||||
],
|
],
|
||||||
"no-unused-vars": "off",
|
'no-unused-vars': 'off',
|
||||||
"react/prop-types": "off",
|
'react/prop-types': 'off',
|
||||||
"@typescript-eslint/no-unused-vars": [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
"error"
|
'error'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
11
.github/workflows/fastlane.yml
vendored
@@ -27,6 +27,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- 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
|
- name: Set up Ruby 2.6
|
||||||
uses: actions/setup-ruby@v1
|
uses: actions/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
@@ -38,9 +41,15 @@ jobs:
|
|||||||
- name: Install Node dependencies
|
- name: Install Node dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Generate APK
|
- 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
|
run: fastlane android beta
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: jellyfin-audio-player-android.apk
|
name: jellyfin-audio-player-android-${{ steps.vars.outputs.sha_short }}.apk
|
||||||
path: android/app/build/outputs/**/*.apk
|
path: android/app/build/outputs/**/*.apk
|
||||||
7
.gitignore
vendored
@@ -63,4 +63,9 @@ buck-out/
|
|||||||
/ios/Pods/
|
/ios/Pods/
|
||||||
|
|
||||||
build/
|
build/
|
||||||
fastlane/report.xml
|
fastlane/report.xml
|
||||||
|
fastlane/Appfile
|
||||||
|
certificates/
|
||||||
|
|
||||||
|
.env
|
||||||
|
sentry.properties
|
||||||
3
Gemfile
@@ -7,3 +7,6 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
|||||||
# gem "rails"
|
# gem "rails"
|
||||||
|
|
||||||
gem "fastlane", "~> 2.153"
|
gem "fastlane", "~> 2.153"
|
||||||
|
|
||||||
|
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||||
|
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||||
|
|||||||
91
Gemfile.lock
@@ -1,27 +1,28 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.2)
|
CFPropertyList (3.0.3)
|
||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.345.0)
|
aws-partitions (1.426.0)
|
||||||
aws-sdk-core (3.104.3)
|
aws-sdk-core (3.112.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.36.0)
|
aws-sdk-kms (1.42.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.75.0)
|
aws-sdk-s3 (1.88.0)
|
||||||
aws-sdk-core (~> 3, >= 3.104.1)
|
aws-sdk-core (~> 3, >= 3.112.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.2.1)
|
aws-sigv4 (1.2.2)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.4)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
@@ -29,24 +30,28 @@ GEM
|
|||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
digest-crc (0.6.1)
|
digest-crc (0.6.3)
|
||||||
rake (~> 13.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.0.0)
|
emoji_regex (3.2.1)
|
||||||
excon (0.75.0)
|
excon (0.79.0)
|
||||||
faraday (1.0.1)
|
faraday (1.3.0)
|
||||||
|
faraday-net_http (~> 1.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
ruby2_keywords
|
||||||
faraday (>= 0.7.4)
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
|
faraday-net_http (1.0.1)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.0)
|
fastimage (2.2.2)
|
||||||
fastlane (2.153.1)
|
fastlane (2.174.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
@@ -80,6 +85,7 @@ GEM
|
|||||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
xcpretty (~> 0.3.0)
|
xcpretty (~> 0.3.0)
|
||||||
xcpretty-travis-formatter (>= 0.0.3)
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
|
fastlane-plugin-sentry (1.8.0)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.38.0)
|
google-api-client (0.38.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
@@ -89,20 +95,35 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
signet (~> 0.12)
|
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.2.0)
|
||||||
|
google-apis-core (~> 0.1)
|
||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.3)
|
google-cloud-env (1.4.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.26.2)
|
google-cloud-storage (1.30.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-apis-iamcredentials_v1 (~> 0.1)
|
||||||
|
google-apis-storage_v1 (~> 0.1)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.13.0)
|
googleauth (0.15.1)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@@ -114,28 +135,30 @@ GEM
|
|||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.1)
|
json (2.5.1)
|
||||||
jwt (2.2.1)
|
jwt (2.2.2)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.0.0)
|
multipart-post (2.0.0)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
naturally (2.2.0)
|
naturally (2.2.1)
|
||||||
os (1.1.0)
|
os (1.1.1)
|
||||||
plist (3.5.0)
|
plist (3.6.0)
|
||||||
public_suffix (4.0.5)
|
public_suffix (4.0.6)
|
||||||
rake (13.0.1)
|
rake (13.0.3)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
|
rexml (3.2.4)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
|
ruby2_keywords (0.0.4)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.14.1)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
@@ -156,8 +179,9 @@ GEM
|
|||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
|
webrick (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.17.1)
|
xcodeproj (1.19.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
@@ -165,7 +189,7 @@ GEM
|
|||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
xcpretty (0.3.0)
|
xcpretty (0.3.0)
|
||||||
rouge (~> 2.0.7)
|
rouge (~> 2.0.7)
|
||||||
xcpretty-travis-formatter (1.0.0)
|
xcpretty-travis-formatter (1.0.1)
|
||||||
xcpretty (~> 0.2, >= 0.0.7)
|
xcpretty (~> 0.2, >= 0.0.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
@@ -173,6 +197,7 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
fastlane (~> 2.153)
|
fastlane (~> 2.153)
|
||||||
|
fastlane-plugin-sentry
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.17.2
|
1.17.2
|
||||||
|
|||||||
66
README.md
@@ -1,25 +1,69 @@
|
|||||||
# Jellyfin Audio Player
|
# Jellyfin Audio Player
|
||||||
This is a React Native-based audio streaming app for Jellyfin.
|

|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|||||
|
|||||
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
|
|
||||||
|
## 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
|
## 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
|
### 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
|
### Development Build
|
||||||
This app has been mainly developed for iOS, but should mostly function on Android as well. To get started, do the following:
|
As soon as all prerequisites are covered, you can start development in either iOS or Android simulators by running the following
|
||||||
1. Clone this repository
|
```
|
||||||
2. Install [NodeJS](https://nodejs.org/en/) and [XCode](https://developer.apple.com/download/)
|
npm run ios
|
||||||
3. `npm install`
|
npm run android
|
||||||
4. `npm run ios`
|
```
|
||||||
|
|
||||||
### Production Build
|
### 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.
|
||||||
@@ -79,10 +79,11 @@ import com.android.build.OutputFile
|
|||||||
|
|
||||||
project.ext.react = [
|
project.ext.react = [
|
||||||
enableHermes: false, // clean and rebuild if changing
|
enableHermes: false, // clean and rebuild if changing
|
||||||
entryFile: 'index.ts'
|
entryFile: 'index.js'
|
||||||
]
|
]
|
||||||
|
|
||||||
apply from: "../../node_modules/react-native/react.gradle"
|
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:
|
* Set this to true to create two separate APKs instead of one:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
||||||
# Version of flipper SDK to use with React Native
|
# Version of flipper SDK to use with React Native
|
||||||
FLIPPER_VERSION=0.33.1
|
FLIPPER_VERSION=0.54.0
|
||||||
|
|||||||
2
app.json
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "JellyfinAudioPlayer",
|
"name": "JellyfinAudioPlayer",
|
||||||
"displayName": "Audio Player"
|
"displayName": "Jellyfin Player"
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: ['module:metro-react-native-babel-preset'],
|
presets: [
|
||||||
|
'module:metro-react-native-babel-preset',
|
||||||
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
'module-resolver',
|
'module-resolver',
|
||||||
{
|
{
|
||||||
// root: ['./src'],
|
root: ['.'],
|
||||||
|
extensions: [
|
||||||
|
'.ios.ts',
|
||||||
|
'.android.ts',
|
||||||
|
'.ts',
|
||||||
|
'.ios.tsx',
|
||||||
|
'.android.tsx',
|
||||||
|
'.tsx',
|
||||||
|
'.jsx',
|
||||||
|
'.js',
|
||||||
|
'.json',
|
||||||
|
],
|
||||||
alias: {
|
alias: {
|
||||||
store: './src/store',
|
store: './src/store',
|
||||||
components: './src/components',
|
components: './src/components',
|
||||||
utility: './src/utility',
|
utility: './src/utility',
|
||||||
screens: './src/screens',
|
screens: './src/screens',
|
||||||
assets: './src/assets',
|
assets: './src/assets',
|
||||||
|
'@localisation': './src/localisation',
|
||||||
CONSTANTS: './src/CONSTANTS',
|
CONSTANTS: './src/CONSTANTS',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'module:react-native-dotenv'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
docs/images/bmd-logo-icon.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
@@ -1,2 +1,4 @@
|
|||||||
package_name("org.leinelissen.jellyfinaudioplayer")
|
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||||
app_identifier("org.leinelissen.JellyfinAudioPlayer")
|
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||||
|
apple_id("lei@moeilijkedingen.nl")
|
||||||
|
team_id("238P3C58WC")
|
||||||
@@ -1,23 +1,86 @@
|
|||||||
default_platform(:ios)
|
default_platform(:ios)
|
||||||
|
|
||||||
platform :ios do
|
platform :ios do
|
||||||
lane :beta do
|
lane :alpha do
|
||||||
enable_automatic_code_signing
|
get_certificates(
|
||||||
|
development: true,
|
||||||
|
output_path: 'certificates/'
|
||||||
|
)
|
||||||
build_app(
|
build_app(
|
||||||
scheme: "JellyfinAudioPlayer",
|
scheme: "Jellyfin Player",
|
||||||
export_method: "development",
|
export_method: "development",
|
||||||
output_directory: "build",
|
output_directory: "build",
|
||||||
workspace: "ios/JellyfinAudioPlayer.xcworkspace"
|
workspace: "ios/JellyfinAudioPlayer.xcworkspace"
|
||||||
)
|
)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
platform :android do
|
platform :android do
|
||||||
desc "Generate beta build"
|
desc "Generate beta build"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
gradle(
|
gradle(
|
||||||
task: "clean assembleRelease",
|
task: "clean assembleRelease",
|
||||||
project_dir: "android"
|
project_dir: "android"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
5
fastlane/Pluginfile
Normal 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
@@ -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).
|
||||||
@@ -1,18 +1,11 @@
|
|||||||
// 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 'react-native-gesture-handler';
|
||||||
import { AppRegistry } from 'react-native';
|
import { AppRegistry } from 'react-native';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import App from './src/components/App';
|
import App from './src/components/App';
|
||||||
import { name as appName } from './app.json';
|
import { name as appName } from './app.json';
|
||||||
import PlaybackService from './src/utility/PlaybackService';
|
import PlaybackService from './src/utility/PlaybackService';
|
||||||
|
import { setupSentry } from 'utility/Sentry';
|
||||||
|
|
||||||
|
setupSentry();
|
||||||
AppRegistry.registerComponent(appName, () => App);
|
AppRegistry.registerComponent(appName, () => App);
|
||||||
TrackPlayer.registerPlaybackService(() => PlaybackService);
|
TrackPlayer.registerPlaybackService(() => PlaybackService);
|
||||||
8
ios/File.swift
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
// JellyfinAudioPlayer
|
||||||
|
//
|
||||||
|
// Created by Lei Nelissen on 02/11/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
4
ios/JellyfinAudioPlayer-Bridging-Header.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
//
|
||||||
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
|
//
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||||
463612208457EBB4B723000A /* libPods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 842AB597D56E84A4ACDC4735 /* libPods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.a */; };
|
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 */; };
|
4FA1B23D2550A94C007A035E /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FA1B23C2550A94C007A035E /* File.swift */; };
|
||||||
A807E2BB233D6F9347D8A95C /* libPods-JellyfinAudioPlayer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 71370E61E2CC6BD9372ADCF3 /* libPods-JellyfinAudioPlayer.a */; };
|
A807E2BB233D6F9347D8A95C /* libPods-JellyfinAudioPlayer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 71370E61E2CC6BD9372ADCF3 /* libPods-JellyfinAudioPlayer.a */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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; };
|
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>"; };
|
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>"; };
|
00E356F21AD99517003FC87E /* JellyfinAudioPlayerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JellyfinAudioPlayerTests.m; sourceTree = "<group>"; };
|
||||||
@@ -41,6 +40,8 @@
|
|||||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = JellyfinAudioPlayer/main.m; 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>"; };
|
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>"; };
|
||||||
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>"; };
|
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>"; };
|
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; };
|
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; };
|
7D43C7610851B9666193E3F6 /* libPods-JellyfinAudioPlayer-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JellyfinAudioPlayer-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -95,13 +96,14 @@
|
|||||||
13B07FAE1A68108700A75B9A /* JellyfinAudioPlayer */ = {
|
13B07FAE1A68108700A75B9A /* JellyfinAudioPlayer */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
|
|
||||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||||
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
||||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||||
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
|
||||||
13B07FB71A68108700A75B9A /* main.m */,
|
13B07FB71A68108700A75B9A /* main.m */,
|
||||||
|
4FA1B23C2550A94C007A035E /* File.swift */,
|
||||||
|
4FA1B23B2550A94B007A035E /* JellyfinAudioPlayer-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
name = JellyfinAudioPlayer;
|
name = JellyfinAudioPlayer;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -199,6 +201,7 @@
|
|||||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||||
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */,
|
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */,
|
||||||
|
1DC46C84C90B4D84A18AC142 /* Upload Debug Symbols to Sentry */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -219,13 +222,13 @@
|
|||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
00E356ED1AD99517003FC87E = {
|
00E356ED1AD99517003FC87E = {
|
||||||
CreatedOnToolsVersion = 6.2;
|
CreatedOnToolsVersion = 6.2;
|
||||||
DevelopmentTeam = HD2D35G9Y4;
|
DevelopmentTeam = 238P3C58WC;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||||
};
|
};
|
||||||
13B07F861A680F5B00A75B9A = {
|
13B07F861A680F5B00A75B9A = {
|
||||||
DevelopmentTeam = HD2D35G9Y4;
|
DevelopmentTeam = 238P3C58WC;
|
||||||
LastSwiftMigration = 1120;
|
LastSwiftMigration = 1210;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -261,7 +264,6 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4F46F441249A56FF00308470 /* main.jsbundle in Resources */,
|
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
|
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
|
||||||
);
|
);
|
||||||
@@ -275,14 +277,31 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
);
|
);
|
||||||
name = "Bundle React Native code and images";
|
name = "Bundle React Native code and images";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/main.jsbundle",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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";
|
||||||
|
};
|
||||||
|
1DC46C84C90B4D84A18AC142 /* Upload Debug Symbols to Sentry */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Upload Debug Symbols to Sentry";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym";
|
||||||
};
|
};
|
||||||
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */ = {
|
B9FB8FC65CEFF9AFAC71127E /* [CP] Copy Pods Resources */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
@@ -399,6 +418,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
|
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
|
||||||
|
4FA1B23D2550A94C007A035E /* File.swift in Sources */,
|
||||||
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
13B07FC11A68108700A75B9A /* main.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -430,9 +450,10 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 8DAD3DCD6450C4255A20940E /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.debug.xcconfig */;
|
baseConfigurationReference = 8DAD3DCD6450C4255A20940E /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -455,10 +476,11 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 39572B38534BBDBB596C8C95 /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.release.xcconfig */;
|
baseConfigurationReference = 39572B38534BBDBB596C8C95 /* Pods-JellyfinAudioPlayer-JellyfinAudioPlayerTests.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
INFOPLIST_FILE = JellyfinAudioPlayerTests/Info.plist;
|
INFOPLIST_FILE = JellyfinAudioPlayerTests/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
@@ -469,6 +491,8 @@
|
|||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
PROVISIONING_PROFILE = "915c5213-22f6-4f9d-8065-2a06300f9bfb";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JellyfinAudioPlayer.app/JellyfinAudioPlayer";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/JellyfinAudioPlayer.app/JellyfinAudioPlayer";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -480,8 +504,8 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -494,8 +518,10 @@
|
|||||||
"-ObjC",
|
"-ObjC",
|
||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.leinelissen.JellyfinAudioPlayer;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
|
||||||
PRODUCT_NAME = "Jellyfin Player";
|
PRODUCT_NAME = "Jellyfin Player";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "JellyfinAudioPlayer-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
@@ -509,8 +535,8 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
DEVELOPMENT_TEAM = HD2D35G9Y4;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
INFOPLIST_FILE = JellyfinAudioPlayer/Info.plist;
|
INFOPLIST_FILE = JellyfinAudioPlayer/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
@@ -518,8 +544,11 @@
|
|||||||
"-ObjC",
|
"-ObjC",
|
||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.leinelissen.JellyfinAudioPlayer;
|
PRODUCT_BUNDLE_IDENTIFIER = nl.moeilijkedingen.jellyfinaudioplayer;
|
||||||
PRODUCT_NAME = "Jellyfin Player";
|
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;
|
SWIFT_VERSION = 5.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
};
|
};
|
||||||
@@ -553,10 +582,12 @@
|
|||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = 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;
|
COPY_PHASE_STRIP = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
@@ -572,7 +603,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||||
@@ -581,6 +612,7 @@
|
|||||||
);
|
);
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
PRODUCT_NAME = "Jellyfin Player";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -613,10 +645,12 @@
|
|||||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = 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;
|
COPY_PHASE_STRIP = YES;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
@@ -625,7 +659,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
|
||||||
LIBRARY_SEARCH_PATHS = (
|
LIBRARY_SEARCH_PATHS = (
|
||||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||||
@@ -633,6 +667,7 @@
|
|||||||
"\"$(inherited)\"",
|
"\"$(inherited)\"",
|
||||||
);
|
);
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
PRODUCT_NAME = "Jellyfin Player";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>11</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
<string>processing</string>
|
<string>fetch</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
|
|||||||
@@ -19,6 +19,6 @@
|
|||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>11</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -17,5 +17,8 @@ target 'JellyfinAudioPlayer' do
|
|||||||
use_flipper!
|
use_flipper!
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
flipper_post_install(installer)
|
flipper_post_install(installer)
|
||||||
|
installer.pods_project.build_configurations.each do |config|
|
||||||
|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
520
ios/Podfile.lock
@@ -3,58 +3,58 @@ PODS:
|
|||||||
- CocoaAsyncSocket (7.6.4)
|
- CocoaAsyncSocket (7.6.4)
|
||||||
- CocoaLibEvent (1.0.0)
|
- CocoaLibEvent (1.0.0)
|
||||||
- DoubleConversion (1.1.6)
|
- DoubleConversion (1.1.6)
|
||||||
- FBLazyVector (0.63.2)
|
- FBLazyVector (0.63.4)
|
||||||
- FBReactNativeSpec (0.63.2):
|
- FBReactNativeSpec (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTRequired (= 0.63.2)
|
- RCTRequired (= 0.63.4)
|
||||||
- RCTTypeSafety (= 0.63.2)
|
- RCTTypeSafety (= 0.63.4)
|
||||||
- React-Core (= 0.63.2)
|
- React-Core (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- Flipper (0.41.5):
|
- Flipper (0.54.0):
|
||||||
- Flipper-Folly (~> 2.2)
|
- Flipper-Folly (~> 2.2)
|
||||||
- Flipper-RSocket (~> 1.1)
|
- Flipper-RSocket (~> 1.1)
|
||||||
- Flipper-DoubleConversion (1.1.7)
|
- Flipper-DoubleConversion (1.1.7)
|
||||||
- Flipper-Folly (2.2.0):
|
- Flipper-Folly (2.3.0):
|
||||||
- boost-for-react-native
|
- boost-for-react-native
|
||||||
- CocoaLibEvent (~> 1.0)
|
- CocoaLibEvent (~> 1.0)
|
||||||
- Flipper-DoubleConversion
|
- Flipper-DoubleConversion
|
||||||
- Flipper-Glog
|
- Flipper-Glog
|
||||||
- OpenSSL-Universal (= 1.0.2.19)
|
- OpenSSL-Universal (= 1.0.2.20)
|
||||||
- Flipper-Glog (0.3.6)
|
- Flipper-Glog (0.3.6)
|
||||||
- Flipper-PeerTalk (0.0.4)
|
- Flipper-PeerTalk (0.0.4)
|
||||||
- Flipper-RSocket (1.1.0):
|
- Flipper-RSocket (1.1.0):
|
||||||
- Flipper-Folly (~> 2.2)
|
- Flipper-Folly (~> 2.2)
|
||||||
- FlipperKit (0.41.5):
|
- FlipperKit (0.54.0):
|
||||||
- FlipperKit/Core (= 0.41.5)
|
- FlipperKit/Core (= 0.54.0)
|
||||||
- FlipperKit/Core (0.41.5):
|
- FlipperKit/Core (0.54.0):
|
||||||
- Flipper (~> 0.41.5)
|
- Flipper (~> 0.54.0)
|
||||||
- FlipperKit/CppBridge
|
- FlipperKit/CppBridge
|
||||||
- FlipperKit/FBCxxFollyDynamicConvert
|
- FlipperKit/FBCxxFollyDynamicConvert
|
||||||
- FlipperKit/FBDefines
|
- FlipperKit/FBDefines
|
||||||
- FlipperKit/FKPortForwarding
|
- FlipperKit/FKPortForwarding
|
||||||
- FlipperKit/CppBridge (0.41.5):
|
- FlipperKit/CppBridge (0.54.0):
|
||||||
- Flipper (~> 0.41.5)
|
- Flipper (~> 0.54.0)
|
||||||
- FlipperKit/FBCxxFollyDynamicConvert (0.41.5):
|
- FlipperKit/FBCxxFollyDynamicConvert (0.54.0):
|
||||||
- Flipper-Folly (~> 2.2)
|
- Flipper-Folly (~> 2.2)
|
||||||
- FlipperKit/FBDefines (0.41.5)
|
- FlipperKit/FBDefines (0.54.0)
|
||||||
- FlipperKit/FKPortForwarding (0.41.5):
|
- FlipperKit/FKPortForwarding (0.54.0):
|
||||||
- CocoaAsyncSocket (~> 7.6)
|
- CocoaAsyncSocket (~> 7.6)
|
||||||
- Flipper-PeerTalk (~> 0.0.4)
|
- Flipper-PeerTalk (~> 0.0.4)
|
||||||
- FlipperKit/FlipperKitHighlightOverlay (0.41.5)
|
- FlipperKit/FlipperKitHighlightOverlay (0.54.0)
|
||||||
- FlipperKit/FlipperKitLayoutPlugin (0.41.5):
|
- FlipperKit/FlipperKitLayoutPlugin (0.54.0):
|
||||||
- FlipperKit/Core
|
- FlipperKit/Core
|
||||||
- FlipperKit/FlipperKitHighlightOverlay
|
- FlipperKit/FlipperKitHighlightOverlay
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||||
- YogaKit (~> 1.18)
|
- YogaKit (~> 1.18)
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.41.5)
|
- FlipperKit/FlipperKitLayoutTextSearchable (0.54.0)
|
||||||
- FlipperKit/FlipperKitNetworkPlugin (0.41.5):
|
- FlipperKit/FlipperKitNetworkPlugin (0.54.0):
|
||||||
- FlipperKit/Core
|
- FlipperKit/Core
|
||||||
- FlipperKit/FlipperKitReactPlugin (0.41.5):
|
- FlipperKit/FlipperKitReactPlugin (0.54.0):
|
||||||
- FlipperKit/Core
|
- FlipperKit/Core
|
||||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.41.5):
|
- FlipperKit/FlipperKitUserDefaultsPlugin (0.54.0):
|
||||||
- FlipperKit/Core
|
- FlipperKit/Core
|
||||||
- FlipperKit/SKIOSNetworkPlugin (0.41.5):
|
- FlipperKit/SKIOSNetworkPlugin (0.54.0):
|
||||||
- FlipperKit/Core
|
- FlipperKit/Core
|
||||||
- FlipperKit/FlipperKitNetworkPlugin
|
- FlipperKit/FlipperKitNetworkPlugin
|
||||||
- Folly (2020.01.13.00):
|
- Folly (2020.01.13.00):
|
||||||
@@ -76,269 +76,275 @@ PODS:
|
|||||||
- libwebp/mux (1.1.0):
|
- libwebp/mux (1.1.0):
|
||||||
- libwebp/demux
|
- libwebp/demux
|
||||||
- libwebp/webp (1.1.0)
|
- libwebp/webp (1.1.0)
|
||||||
- OpenSSL-Universal (1.0.2.19):
|
- OpenSSL-Universal (1.0.2.20):
|
||||||
- OpenSSL-Universal/Static (= 1.0.2.19)
|
- OpenSSL-Universal/Static (= 1.0.2.20)
|
||||||
- OpenSSL-Universal/Static (1.0.2.19)
|
- OpenSSL-Universal/Static (1.0.2.20)
|
||||||
- RCTRequired (0.63.2)
|
- RCTRequired (0.63.4)
|
||||||
- RCTTypeSafety (0.63.2):
|
- RCTTypeSafety (0.63.4):
|
||||||
- FBLazyVector (= 0.63.2)
|
- FBLazyVector (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTRequired (= 0.63.2)
|
- RCTRequired (= 0.63.4)
|
||||||
- React-Core (= 0.63.2)
|
- React-Core (= 0.63.4)
|
||||||
- React (0.63.2):
|
- React (0.63.4):
|
||||||
- React-Core (= 0.63.2)
|
- React-Core (= 0.63.4)
|
||||||
- React-Core/DevSupport (= 0.63.2)
|
- React-Core/DevSupport (= 0.63.4)
|
||||||
- React-Core/RCTWebSocket (= 0.63.2)
|
- React-Core/RCTWebSocket (= 0.63.4)
|
||||||
- React-RCTActionSheet (= 0.63.2)
|
- React-RCTActionSheet (= 0.63.4)
|
||||||
- React-RCTAnimation (= 0.63.2)
|
- React-RCTAnimation (= 0.63.4)
|
||||||
- React-RCTBlob (= 0.63.2)
|
- React-RCTBlob (= 0.63.4)
|
||||||
- React-RCTImage (= 0.63.2)
|
- React-RCTImage (= 0.63.4)
|
||||||
- React-RCTLinking (= 0.63.2)
|
- React-RCTLinking (= 0.63.4)
|
||||||
- React-RCTNetwork (= 0.63.2)
|
- React-RCTNetwork (= 0.63.4)
|
||||||
- React-RCTSettings (= 0.63.2)
|
- React-RCTSettings (= 0.63.4)
|
||||||
- React-RCTText (= 0.63.2)
|
- React-RCTText (= 0.63.4)
|
||||||
- React-RCTVibration (= 0.63.2)
|
- React-RCTVibration (= 0.63.4)
|
||||||
- React-callinvoker (0.63.2)
|
- React-callinvoker (0.63.4)
|
||||||
- React-Core (0.63.2):
|
- React-Core (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default (= 0.63.2)
|
- React-Core/Default (= 0.63.4)
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/CoreModulesHeaders (0.63.2):
|
- React-Core/CoreModulesHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/Default (0.63.2):
|
- React-Core/Default (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/DevSupport (0.63.2):
|
- React-Core/DevSupport (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default (= 0.63.2)
|
- React-Core/Default (= 0.63.4)
|
||||||
- React-Core/RCTWebSocket (= 0.63.2)
|
- React-Core/RCTWebSocket (= 0.63.4)
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- React-jsinspector (= 0.63.2)
|
- React-jsinspector (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTActionSheetHeaders (0.63.2):
|
- React-Core/RCTActionSheetHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTAnimationHeaders (0.63.2):
|
- React-Core/RCTAnimationHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTBlobHeaders (0.63.2):
|
- React-Core/RCTBlobHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTImageHeaders (0.63.2):
|
- React-Core/RCTImageHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTLinkingHeaders (0.63.2):
|
- React-Core/RCTLinkingHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTNetworkHeaders (0.63.2):
|
- React-Core/RCTNetworkHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTSettingsHeaders (0.63.2):
|
- React-Core/RCTSettingsHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTTextHeaders (0.63.2):
|
- React-Core/RCTTextHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTVibrationHeaders (0.63.2):
|
- React-Core/RCTVibrationHeaders (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default
|
- React-Core/Default
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-Core/RCTWebSocket (0.63.2):
|
- React-Core/RCTWebSocket (0.63.4):
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-Core/Default (= 0.63.2)
|
- React-Core/Default (= 0.63.4)
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsiexecutor (= 0.63.2)
|
- React-jsiexecutor (= 0.63.4)
|
||||||
- Yoga
|
- Yoga
|
||||||
- React-CoreModules (0.63.2):
|
- React-CoreModules (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTTypeSafety (= 0.63.2)
|
- RCTTypeSafety (= 0.63.4)
|
||||||
- React-Core/CoreModulesHeaders (= 0.63.2)
|
- React-Core/CoreModulesHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-RCTImage (= 0.63.2)
|
- React-RCTImage (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-cxxreact (0.63.2):
|
- React-cxxreact (0.63.4):
|
||||||
- boost-for-react-native (= 1.63.0)
|
- boost-for-react-native (= 1.63.0)
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-callinvoker (= 0.63.2)
|
- React-callinvoker (= 0.63.4)
|
||||||
- React-jsinspector (= 0.63.2)
|
- React-jsinspector (= 0.63.4)
|
||||||
- React-jsi (0.63.2):
|
- React-jsi (0.63.4):
|
||||||
- boost-for-react-native (= 1.63.0)
|
- boost-for-react-native (= 1.63.0)
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-jsi/Default (= 0.63.2)
|
- React-jsi/Default (= 0.63.4)
|
||||||
- React-jsi/Default (0.63.2):
|
- React-jsi/Default (0.63.4):
|
||||||
- boost-for-react-native (= 1.63.0)
|
- boost-for-react-native (= 1.63.0)
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-jsiexecutor (0.63.2):
|
- React-jsiexecutor (0.63.4):
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsinspector (0.63.2)
|
- React-jsinspector (0.63.4)
|
||||||
- react-native-appearance (0.3.4):
|
- react-native-safe-area-context (3.1.9):
|
||||||
- React
|
- React-Core
|
||||||
- react-native-safe-area-context (3.1.1):
|
|
||||||
- React
|
|
||||||
- react-native-slider (3.0.3):
|
- react-native-slider (3.0.3):
|
||||||
- React
|
- React
|
||||||
- react-native-track-player (1.2.3):
|
- react-native-track-player (1.2.3):
|
||||||
- React
|
- React
|
||||||
- react-native-webview (10.3.2):
|
- react-native-webview (11.2.3):
|
||||||
- React
|
- React-Core
|
||||||
- React-RCTActionSheet (0.63.2):
|
- React-RCTActionSheet (0.63.4):
|
||||||
- React-Core/RCTActionSheetHeaders (= 0.63.2)
|
- React-Core/RCTActionSheetHeaders (= 0.63.4)
|
||||||
- React-RCTAnimation (0.63.2):
|
- React-RCTAnimation (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTTypeSafety (= 0.63.2)
|
- RCTTypeSafety (= 0.63.4)
|
||||||
- React-Core/RCTAnimationHeaders (= 0.63.2)
|
- React-Core/RCTAnimationHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-RCTBlob (0.63.2):
|
- React-RCTBlob (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- React-Core/RCTBlobHeaders (= 0.63.2)
|
- React-Core/RCTBlobHeaders (= 0.63.4)
|
||||||
- React-Core/RCTWebSocket (= 0.63.2)
|
- React-Core/RCTWebSocket (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-RCTNetwork (= 0.63.2)
|
- React-RCTNetwork (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-RCTImage (0.63.2):
|
- React-RCTImage (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTTypeSafety (= 0.63.2)
|
- RCTTypeSafety (= 0.63.4)
|
||||||
- React-Core/RCTImageHeaders (= 0.63.2)
|
- React-Core/RCTImageHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- React-RCTNetwork (= 0.63.2)
|
- React-RCTNetwork (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-RCTLinking (0.63.2):
|
- React-RCTLinking (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- React-Core/RCTLinkingHeaders (= 0.63.2)
|
- React-Core/RCTLinkingHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-RCTNetwork (0.63.2):
|
- React-RCTNetwork (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTTypeSafety (= 0.63.2)
|
- RCTTypeSafety (= 0.63.4)
|
||||||
- React-Core/RCTNetworkHeaders (= 0.63.2)
|
- React-Core/RCTNetworkHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-RCTSettings (0.63.2):
|
- React-RCTSettings (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- RCTTypeSafety (= 0.63.2)
|
- RCTTypeSafety (= 0.63.4)
|
||||||
- React-Core/RCTSettingsHeaders (= 0.63.2)
|
- React-Core/RCTSettingsHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- React-RCTText (0.63.2):
|
- React-RCTText (0.63.4):
|
||||||
- React-Core/RCTTextHeaders (= 0.63.2)
|
- React-Core/RCTTextHeaders (= 0.63.4)
|
||||||
- React-RCTVibration (0.63.2):
|
- React-RCTVibration (0.63.4):
|
||||||
- FBReactNativeSpec (= 0.63.2)
|
- FBReactNativeSpec (= 0.63.4)
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- React-Core/RCTVibrationHeaders (= 0.63.2)
|
- React-Core/RCTVibrationHeaders (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (= 0.63.2)
|
- ReactCommon/turbomodule/core (= 0.63.4)
|
||||||
- ReactCommon/turbomodule/core (0.63.2):
|
- ReactCommon/turbomodule/core (0.63.4):
|
||||||
- DoubleConversion
|
- DoubleConversion
|
||||||
- Folly (= 2020.01.13.00)
|
- Folly (= 2020.01.13.00)
|
||||||
- glog
|
- glog
|
||||||
- React-callinvoker (= 0.63.2)
|
- React-callinvoker (= 0.63.4)
|
||||||
- React-Core (= 0.63.2)
|
- React-Core (= 0.63.4)
|
||||||
- React-cxxreact (= 0.63.2)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.2)
|
- React-jsi (= 0.63.4)
|
||||||
- RNCAsyncStorage (1.11.0):
|
- RNCAsyncStorage (1.12.1):
|
||||||
- React
|
- React-Core
|
||||||
- RNCMaskedView (0.1.10):
|
- RNCMaskedView (0.1.10):
|
||||||
- React
|
- React
|
||||||
- RNCPicker (1.6.5):
|
- RNCPicker (1.8.1):
|
||||||
- React
|
- React-Core
|
||||||
- RNFastImage (8.3.2):
|
- RNFastImage (8.3.4):
|
||||||
- React
|
- React-Core
|
||||||
- SDWebImage (~> 5.8)
|
- SDWebImage (~> 5.8)
|
||||||
- SDWebImageWebPCoder (~> 0.6.1)
|
- SDWebImageWebPCoder (~> 0.6.1)
|
||||||
- RNGestureHandler (1.7.0):
|
- RNGestureHandler (1.10.0):
|
||||||
- React
|
- React-Core
|
||||||
- RNReanimated (1.10.1):
|
- RNLocalize (2.0.1):
|
||||||
- React
|
- React-Core
|
||||||
- RNScreens (2.9.0):
|
- RNReanimated (1.13.2):
|
||||||
- React
|
- React-Core
|
||||||
|
- RNScreens (2.17.1):
|
||||||
|
- React-Core
|
||||||
|
- RNSentry (2.2.0):
|
||||||
|
- React-Core
|
||||||
|
- Sentry (= 6.1.4)
|
||||||
- RNSVG (12.1.0):
|
- RNSVG (12.1.0):
|
||||||
- React
|
- React
|
||||||
- SDWebImage (5.8.1):
|
- SDWebImage (5.10.4):
|
||||||
- SDWebImage/Core (= 5.8.1)
|
- SDWebImage/Core (= 5.10.4)
|
||||||
- SDWebImage/Core (5.8.1)
|
- SDWebImage/Core (5.10.4)
|
||||||
- SDWebImageWebPCoder (0.6.1):
|
- SDWebImageWebPCoder (0.6.1):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.7)
|
- SDWebImage/Core (~> 5.7)
|
||||||
|
- Sentry (6.1.4):
|
||||||
|
- Sentry/Core (= 6.1.4)
|
||||||
|
- Sentry/Core (6.1.4)
|
||||||
- Yoga (1.14.0)
|
- Yoga (1.14.0)
|
||||||
- YogaKit (1.18.1):
|
- YogaKit (1.18.1):
|
||||||
- Yoga (~> 1.14)
|
- Yoga (~> 1.14)
|
||||||
@@ -347,25 +353,25 @@ DEPENDENCIES:
|
|||||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||||
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
|
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
|
||||||
- Flipper (~> 0.41.1)
|
- Flipper (~> 0.54.0)
|
||||||
- Flipper-DoubleConversion (= 1.1.7)
|
- Flipper-DoubleConversion (= 1.1.7)
|
||||||
- Flipper-Folly (~> 2.2)
|
- Flipper-Folly (~> 2.2)
|
||||||
- Flipper-Glog (= 0.3.6)
|
- Flipper-Glog (= 0.3.6)
|
||||||
- Flipper-PeerTalk (~> 0.0.4)
|
- Flipper-PeerTalk (~> 0.0.4)
|
||||||
- Flipper-RSocket (~> 1.1)
|
- Flipper-RSocket (~> 1.1)
|
||||||
- FlipperKit (~> 0.41.1)
|
- FlipperKit (~> 0.54.0)
|
||||||
- FlipperKit/Core (~> 0.41.1)
|
- FlipperKit/Core (~> 0.54.0)
|
||||||
- FlipperKit/CppBridge (~> 0.41.1)
|
- FlipperKit/CppBridge (~> 0.54.0)
|
||||||
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.41.1)
|
- FlipperKit/FBCxxFollyDynamicConvert (~> 0.54.0)
|
||||||
- FlipperKit/FBDefines (~> 0.41.1)
|
- FlipperKit/FBDefines (~> 0.54.0)
|
||||||
- FlipperKit/FKPortForwarding (~> 0.41.1)
|
- FlipperKit/FKPortForwarding (~> 0.54.0)
|
||||||
- FlipperKit/FlipperKitHighlightOverlay (~> 0.41.1)
|
- FlipperKit/FlipperKitHighlightOverlay (~> 0.54.0)
|
||||||
- FlipperKit/FlipperKitLayoutPlugin (~> 0.41.1)
|
- FlipperKit/FlipperKitLayoutPlugin (~> 0.54.0)
|
||||||
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.41.1)
|
- FlipperKit/FlipperKitLayoutTextSearchable (~> 0.54.0)
|
||||||
- FlipperKit/FlipperKitNetworkPlugin (~> 0.41.1)
|
- FlipperKit/FlipperKitNetworkPlugin (~> 0.54.0)
|
||||||
- FlipperKit/FlipperKitReactPlugin (~> 0.41.1)
|
- FlipperKit/FlipperKitReactPlugin (~> 0.54.0)
|
||||||
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.41.1)
|
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.54.0)
|
||||||
- FlipperKit/SKIOSNetworkPlugin (~> 0.41.1)
|
- FlipperKit/SKIOSNetworkPlugin (~> 0.54.0)
|
||||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||||
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
||||||
@@ -380,7 +386,6 @@ DEPENDENCIES:
|
|||||||
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
|
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
|
||||||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
- 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-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||||
- react-native-track-player (from `../node_modules/react-native-track-player`)
|
- react-native-track-player (from `../node_modules/react-native-track-player`)
|
||||||
@@ -400,8 +405,10 @@ DEPENDENCIES:
|
|||||||
- "RNCPicker (from `../node_modules/@react-native-community/picker`)"
|
- "RNCPicker (from `../node_modules/@react-native-community/picker`)"
|
||||||
- RNFastImage (from `../node_modules/react-native-fast-image`)
|
- RNFastImage (from `../node_modules/react-native-fast-image`)
|
||||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||||
|
- RNLocalize (from `../node_modules/react-native-localize`)
|
||||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||||
- RNScreens (from `../node_modules/react-native-screens`)
|
- RNScreens (from `../node_modules/react-native-screens`)
|
||||||
|
- "RNSentry (from `../node_modules/@sentry/react-native`)"
|
||||||
- RNSVG (from `../node_modules/react-native-svg`)
|
- RNSVG (from `../node_modules/react-native-svg`)
|
||||||
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||||
|
|
||||||
@@ -421,6 +428,7 @@ SPEC REPOS:
|
|||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
|
- Sentry
|
||||||
- YogaKit
|
- YogaKit
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@@ -454,8 +462,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
|
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
|
||||||
React-jsinspector:
|
React-jsinspector:
|
||||||
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
||||||
react-native-appearance:
|
|
||||||
:path: "../node_modules/react-native-appearance"
|
|
||||||
react-native-safe-area-context:
|
react-native-safe-area-context:
|
||||||
:path: "../node_modules/react-native-safe-area-context"
|
:path: "../node_modules/react-native-safe-area-context"
|
||||||
react-native-slider:
|
react-native-slider:
|
||||||
@@ -494,10 +500,14 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native-fast-image"
|
:path: "../node_modules/react-native-fast-image"
|
||||||
RNGestureHandler:
|
RNGestureHandler:
|
||||||
:path: "../node_modules/react-native-gesture-handler"
|
:path: "../node_modules/react-native-gesture-handler"
|
||||||
|
RNLocalize:
|
||||||
|
:path: "../node_modules/react-native-localize"
|
||||||
RNReanimated:
|
RNReanimated:
|
||||||
:path: "../node_modules/react-native-reanimated"
|
:path: "../node_modules/react-native-reanimated"
|
||||||
RNScreens:
|
RNScreens:
|
||||||
:path: "../node_modules/react-native-screens"
|
:path: "../node_modules/react-native-screens"
|
||||||
|
RNSentry:
|
||||||
|
:path: "../node_modules/@sentry/react-native"
|
||||||
RNSVG:
|
RNSVG:
|
||||||
:path: "../node_modules/react-native-svg"
|
:path: "../node_modules/react-native-svg"
|
||||||
Yoga:
|
Yoga:
|
||||||
@@ -508,57 +518,59 @@ SPEC CHECKSUMS:
|
|||||||
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
|
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
|
||||||
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
|
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
|
||||||
DoubleConversion: cde416483dac037923206447da6e1454df403714
|
DoubleConversion: cde416483dac037923206447da6e1454df403714
|
||||||
FBLazyVector: 3ef4a7f62e7db01092f9d517d2ebc0d0677c4a37
|
FBLazyVector: 3bb422f41b18121b71783a905c10e58606f7dc3e
|
||||||
FBReactNativeSpec: dc7fa9088f0f2a998503a352b0554d69a4391c5a
|
FBReactNativeSpec: f2c97f2529dd79c083355182cc158c9f98f4bd6e
|
||||||
Flipper: 33585e2d9810fe5528346be33bcf71b37bb7ae13
|
Flipper: be611d4b742d8c87fbae2ca5f44603a02539e365
|
||||||
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
|
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
|
||||||
Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
|
Flipper-Folly: e4493b013c02d9347d5e0cb4d128680239f6c78a
|
||||||
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
|
Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
|
||||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||||
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
|
Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
|
||||||
FlipperKit: bc68102cd4952a258a23c9c1b316c7bec1fecf83
|
FlipperKit: ab353d41aea8aae2ea6daaf813e67496642f3d7d
|
||||||
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
|
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
|
||||||
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
|
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
|
||||||
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
||||||
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
OpenSSL-Universal: ff34003318d5e1163e9529b08470708e389ffcdd
|
||||||
RCTRequired: f13f25e7b12f925f1f6a6a8c69d929a03c0129fe
|
RCTRequired: 082f10cd3f905d6c124597fd1c14f6f2655ff65e
|
||||||
RCTTypeSafety: 44982c5c8e43ff4141eb519a8ddc88059acd1f3a
|
RCTTypeSafety: 8c9c544ecbf20337d069e4ae7fd9a377aadf504b
|
||||||
React: e1c65dd41cb9db13b99f24608e47dd595f28ca9a
|
React: b0a957a2c44da4113b0c4c9853d8387f8e64e615
|
||||||
React-callinvoker: 552a6a6bc8b3bb794cf108ad59e5a9e2e3b4fc98
|
React-callinvoker: c3f44dd3cb195b6aa46621fff95ded79d59043fe
|
||||||
React-Core: 9d341e725dc9cd2f49e4c49ad1fc4e8776aa2639
|
React-Core: d3b2a1ac9a2c13c3bcde712d9281fc1c8a5b315b
|
||||||
React-CoreModules: 5335e168165da7f7083ce7147768d36d3e292318
|
React-CoreModules: 0581ff36cb797da0943d424f69e7098e43e9be60
|
||||||
React-cxxreact: d3261ec5f7d11743fbf21e263a34ea51d1f13ebc
|
React-cxxreact: c1480d4fda5720086c90df537ee7d285d4c57ac3
|
||||||
React-jsi: 54245e1d5f4b690dec614a73a3795964eeef13a8
|
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
|
||||||
React-jsiexecutor: 8ca588cc921e70590820ce72b8789b02c67cce38
|
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
|
||||||
React-jsinspector: b14e62ebe7a66e9231e9581279909f2fc3db6606
|
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
|
||||||
react-native-appearance: fc2014516054585d531e07aa0b40ab0de1d2be85
|
react-native-safe-area-context: 86612d2c9a9e94e288319262d10b5f93f0b395f5
|
||||||
react-native-safe-area-context: 4c3249e4840225c61fcd215b136af0a737bccb79
|
|
||||||
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
|
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
|
||||||
react-native-track-player: ba2416753b58f3cdf9db5a07daa65876d659f925
|
react-native-track-player: ba2416753b58f3cdf9db5a07daa65876d659f925
|
||||||
react-native-webview: e2c0bce9a1a7c7edd4eb30f0c3016fce216245ce
|
react-native-webview: 36561eaf7508e67f72d8c959b713bac841f3652e
|
||||||
React-RCTActionSheet: 910163b6b09685a35c4ebbc52b66d1bfbbe39fc5
|
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
|
||||||
React-RCTAnimation: 9a883bbe1e9d2e158d4fb53765ed64c8dc2200c6
|
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
|
||||||
React-RCTBlob: 39cf0ece1927996c4466510e25d2105f67010e13
|
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
|
||||||
React-RCTImage: de355d738727b09ad3692f2a979affbd54b5f378
|
React-RCTImage: c1b1f2d3f43a4a528c8946d6092384b5c880d2f0
|
||||||
React-RCTLinking: 8122f221d395a63364b2c0078ce284214bd04575
|
React-RCTLinking: 35ae4ab9dc0410d1fcbdce4d7623194a27214fb2
|
||||||
React-RCTNetwork: 8f96c7b49ea6a0f28f98258f347b6ad218bc0830
|
React-RCTNetwork: 29ec2696f8d8cfff7331fac83d3e893c95ef43ae
|
||||||
React-RCTSettings: 8a49622aff9c1925f5455fa340b6fe4853d64ab6
|
React-RCTSettings: 60f0691bba2074ef394f95d4c2265ec284e0a46a
|
||||||
React-RCTText: 1b6773e776e4b33f90468c20fe3b16ca3e224bb8
|
React-RCTText: 5c51df3f08cb9dedc6e790161195d12bac06101c
|
||||||
React-RCTVibration: 4d2e726957f4087449739b595f107c0d4b6c2d2d
|
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
|
||||||
ReactCommon: a0a1edbebcac5e91338371b72ffc66aa822792ce
|
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
|
||||||
RNCAsyncStorage: db711e29e5e0500d9bd21aa0c2e397efa45302b1
|
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
|
||||||
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
|
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
|
||||||
RNCPicker: 55b9b4240d0a9eba8733d02616775d4040de2e7d
|
RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4
|
||||||
RNFastImage: e19ba191922e7dab9d932a4d59d62d76660aa222
|
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
|
||||||
RNGestureHandler: b6b359bb800ae399a9c8b27032bdbf7c18f08a08
|
RNGestureHandler: 03f587ba19a7e2d9fe48fb9aa6e33ace5fd07dd5
|
||||||
RNReanimated: c2bb7438b57a3d987bb2e4e6e4bca94787e30b02
|
RNLocalize: dcf0fdb332b37b0d24178e876a7ce4dbbc9c838d
|
||||||
RNScreens: c526239bbe0e957b988dacc8d75ac94ec9cb19da
|
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
|
||||||
|
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
|
||||||
|
RNSentry: 6aeba1adc242fd22a6826acae92f430697b47a9c
|
||||||
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
|
||||||
SDWebImage: e3eae2eda88578db0685a0c88597fdadd9433f05
|
SDWebImage: c666b97e1fa9c64b4909816a903322018f0a9c84
|
||||||
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
|
SDWebImageWebPCoder: d0dac55073088d24b2ac1b191a71a8f8d0adac21
|
||||||
Yoga: 7740b94929bbacbddda59bf115b5317e9a161598
|
Sentry: 9d055e2de30a77685e86b219acf02e59b82091fc
|
||||||
|
Yoga: 4bd86afe9883422a7c4028c00e34790f560923d6
|
||||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||||
|
|
||||||
PODFILE CHECKSUM: 2d28e638928312f9630682b819df0581e381449e
|
PODFILE CHECKSUM: d99c1202f98b3f7477b6a86c9226010b36143469
|
||||||
|
|
||||||
COCOAPODS: 1.9.1
|
COCOAPODS: 1.10.1
|
||||||
|
|||||||
9892
package-lock.json
generated
87
package.json
@@ -1,67 +1,74 @@
|
|||||||
{
|
{
|
||||||
"name": "JellyfinAudioPlayer",
|
"name": "JellyfinAudioPlayer",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
"ios": "react-native run-ios",
|
"ios": "react-native run-ios --scheme \"Jellyfin Player\"",
|
||||||
"start": "react-native start",
|
"start": "react-native start",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
"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'"
|
"build:ios": "react-native bundle --entry-file='index.ts' --bundle-output='./ios/main.jsbundle' --dev=false --platform='ios'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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/masked-view": "^0.1.10",
|
||||||
"@react-native-community/picker": "^1.6.5",
|
"@react-native-community/picker": "^1.8.1",
|
||||||
"@react-native-community/slider": "^3.0.3",
|
"@react-native-community/slider": "^3.0.3",
|
||||||
"@react-navigation/bottom-tabs": "^5.7.2",
|
"@react-navigation/bottom-tabs": "^5.11.8",
|
||||||
"@react-navigation/native": "^5.7.1",
|
"@react-navigation/native": "^5.9.3",
|
||||||
"@react-navigation/stack": "^5.7.1",
|
"@react-navigation/stack": "^5.14.3",
|
||||||
"@reduxjs/toolkit": "^1.4.0",
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
"@types/lodash": "^4.14.158",
|
"@sentry/react-native": "^2.3.0",
|
||||||
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/redux-logger": "^3.0.8",
|
"@types/redux-logger": "^3.0.8",
|
||||||
"@types/styled-components": "^5.1.1",
|
"@types/styled-components": "^5.1.7",
|
||||||
"date-fns": "^2.15.0",
|
"date-fns": "^2.19.0",
|
||||||
"fuse.js": "^6.4.0",
|
"fuse.js": "^6.4.6",
|
||||||
"lodash": "^4.17.19",
|
"i18n-js": "^3.8.0",
|
||||||
"react": "^16.13.1",
|
"lodash": "^4.17.21",
|
||||||
"react-native": "^0.63.2",
|
"react": "16.13.1",
|
||||||
"react-native-appearance": "^0.3.4",
|
"react-native": "^0.63.4",
|
||||||
"react-native-fast-image": "^8.3.2",
|
"react-native-collapsible": "^1.5.3",
|
||||||
"react-native-gesture-handler": "^1.7.0",
|
"react-native-dotenv": "^2.5.1",
|
||||||
"react-native-reanimated": "^1.10.1",
|
"react-native-fast-image": "^8.3.4",
|
||||||
"react-native-safe-area-context": "^3.1.1",
|
"react-native-gesture-handler": "^1.10.3",
|
||||||
"react-native-screens": "^2.9.0",
|
"react-native-localize": "^2.0.2",
|
||||||
|
"react-native-reanimated": "^2.0.0",
|
||||||
|
"react-native-safe-area-context": "^3.2.0",
|
||||||
|
"react-native-screens": "^2.18.1",
|
||||||
"react-native-svg": "^12.1.0",
|
"react-native-svg": "^12.1.0",
|
||||||
"react-native-svg-transformer": "^0.14.3",
|
"react-native-svg-transformer": "^0.14.3",
|
||||||
"react-native-track-player": "github:leinelissen/react-native-track-player",
|
"react-native-track-player": "github:leinelissen/react-native-track-player",
|
||||||
"react-native-webview": "^10.3.2",
|
"react-native-webview": "^11.2.1",
|
||||||
"react-redux": "^7.2.1",
|
"react-redux": "^7.2.2",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"styled-components": "^5.1.1"
|
"styled-components": "^5.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.10.5",
|
"@babel/core": "^7.13.10",
|
||||||
"@babel/runtime": "^7.10.5",
|
"@babel/runtime": "^7.13.10",
|
||||||
"@react-native-community/eslint-config": "^2.0.0",
|
"@react-native-community/eslint-config": "^2.0.0",
|
||||||
"@types/jest": "^26.0.7",
|
"@sentry/cli": "^1.63.1",
|
||||||
"@types/react-native": "^0.63.2",
|
"@types/i18n-js": "^3.8.0",
|
||||||
"@types/react-redux": "^7.1.9",
|
"@types/jest": "^26.0.20",
|
||||||
"@types/react-test-renderer": "16.9.2",
|
"@types/react-native": "^0.63.51",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
"@types/react-redux": "^7.1.16",
|
||||||
"@typescript-eslint/parser": "^3.7.0",
|
"@types/react-test-renderer": "^17.0.1",
|
||||||
"babel-jest": "^26.1.0",
|
"@types/styled-components-react-native": "^5.1.1",
|
||||||
"babel-plugin-module-resolver": "^4.0.0",
|
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||||
"eslint": "^7.5.0",
|
"@typescript-eslint/parser": "^4.17.0",
|
||||||
"eslint-plugin-react-hooks": "^4.0.8",
|
"babel-jest": "^26.6.3",
|
||||||
"jest": "^26.1.0",
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
"metro-react-native-babel-preset": "^0.61.0",
|
"eslint": "^7.21.0",
|
||||||
"react-test-renderer": "^16.13.1",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"typescript": "^3.9.7"
|
"jest": "^26.6.3",
|
||||||
|
"metro-react-native-babel-preset": "^0.65.2",
|
||||||
|
"react-test-renderer": "^17.0.1",
|
||||||
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "react-native",
|
"preset": "react-native",
|
||||||
|
|||||||
BIN
src/assets/app-icon-white.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
1
src/assets/queue-append.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.39 84.08"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M3.66,0A3.62,3.62,0,0,0,0,3.66,3.67,3.67,0,0,0,3.66,7.37h91a3.75,3.75,0,0,0,3.76-3.71A3.71,3.71,0,0,0,94.63,0Zm0,25.54A3.67,3.67,0,0,0,0,29.25a3.62,3.62,0,0,0,3.66,3.66h91a3.71,3.71,0,0,0,3.76-3.66,3.75,3.75,0,0,0-3.76-3.71ZM.05,50.68v11c0,6.93,4.88,11.18,11.77,11.18h14.4V78c0,4,3.76,5.47,6.84,3l13.23-10.6a3.71,3.71,0,0,0,0-6L33.06,53.86c-3-2.35-6.84-.88-6.84,3v4.88H12.89A1.51,1.51,0,0,1,11.23,60V50.68c0-4-2.05-6.44-5.61-6.44S.05,46.73.05,50.68Zm61.18.44a3.69,3.69,0,0,0,0,7.38h33.4a3.69,3.69,0,1,0,0-7.38Zm0,25.59a3.69,3.69,0,0,0,0,7.37h33.4a3.69,3.69,0,1,0,0-7.37Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 758 B |
1
src/assets/queue-prepend.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.39 84.08"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M61.23,7.37h33.4a3.72,3.72,0,0,0,3.76-3.71A3.71,3.71,0,0,0,94.63,0H61.23a3.62,3.62,0,0,0-3.66,3.66A3.64,3.64,0,0,0,61.23,7.37ZM.05,33.4c0,4,2,6.44,5.57,6.44s5.61-2.49,5.61-6.44V24.07a1.51,1.51,0,0,1,1.66-1.71H26.22v4.89c0,3.85,3.86,5.32,6.84,3L46.29,19.68a3.71,3.71,0,0,0,0-6L33.06,3.12c-3.08-2.49-6.84-1-6.84,3v5.08H11.82C4.93,11.18.05,15.43.05,22.36ZM61.23,33h33.4a3.69,3.69,0,1,0,0-7.37H61.23a3.69,3.69,0,0,0,0,7.37ZM3.66,58.54h91a3.75,3.75,0,0,0,3.76-3.71,3.71,3.71,0,0,0-3.76-3.66h-91A3.62,3.62,0,0,0,0,54.83,3.67,3.67,0,0,0,3.66,58.54Zm0,25.54h91a3.71,3.71,0,0,0,3.76-3.66,3.75,3.75,0,0,0-3.76-3.71h-91A3.67,3.67,0,0,0,0,80.42,3.62,3.62,0,0,0,3.66,84.08Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 848 B |
1
src/assets/queue.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.39 74.76"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M4,15.17,12.65,10a2.45,2.45,0,0,0,0-4.11L4,.62C2-.6,0,0,0,2.33V13.46C0,15.71,2.05,16.29,4,15.17ZM31.3,11.75H94.43a3.93,3.93,0,1,0,0-7.86H31.3a3.88,3.88,0,0,0-4,4A3.87,3.87,0,0,0,31.3,11.75ZM4,44.61l8.69-5.17a2.44,2.44,0,0,0,0-4.1L4,30.06c-2-1.17-4-.58-4,1.71V42.91C0,45.15,2.05,45.74,4,44.61ZM31.3,41.29H94.43a3.93,3.93,0,1,0,0-7.86H31.3a3.93,3.93,0,1,0,0,7.86ZM4,74.2,12.65,69a2.44,2.44,0,0,0,0-4.1L4,59.65C2,58.48,0,59,0,61.36V72.5C0,74.74,2.05,75.33,4,74.2ZM31.3,70.79H94.43a3.93,3.93,0,1,0,0-7.86H31.3a3.93,3.93,0,1,0,0,7.86Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 717 B |
1
src/assets/repeat.svg
Normal 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
@@ -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 |
@@ -9,10 +9,10 @@ import {
|
|||||||
TapGestureHandlerGestureEvent
|
TapGestureHandlerGestureEvent
|
||||||
} from 'react-native-gesture-handler';
|
} from 'react-native-gesture-handler';
|
||||||
|
|
||||||
interface LetterContainerProps {
|
// interface LetterContainerProps {
|
||||||
onPress: (letter: string) => void;
|
// onPress: (letter: string) => void;
|
||||||
letter: string;
|
// letter: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { AppearanceProvider, Appearance, AppearanceListener } from 'react-native-appearance';
|
|
||||||
import Routes from '../screens';
|
import Routes from '../screens';
|
||||||
import store, { persistedStore } from 'store';
|
import store, { persistedStore } from 'store';
|
||||||
import {
|
import {
|
||||||
@@ -10,60 +9,41 @@ import {
|
|||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
DarkTheme,
|
DarkTheme,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
|
import { useColorScheme } from 'react-native';
|
||||||
|
import { ColorSchemeContext, themes } from './Colors';
|
||||||
|
import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
||||||
|
|
||||||
interface State {
|
export default function App(): JSX.Element {
|
||||||
isReady: boolean;
|
const colorScheme = useColorScheme();
|
||||||
colorScheme?: string;
|
// const colorScheme = 'dark';
|
||||||
}
|
const theme = themes[colorScheme || 'light'];
|
||||||
|
|
||||||
export default class App extends Component<{}, State> {
|
useEffect(() => {
|
||||||
state: State = {
|
async function setupTrackPlayer() {
|
||||||
isReady: false,
|
await TrackPlayer.setupPlayer();
|
||||||
};
|
await TrackPlayer.updateOptions({
|
||||||
|
capabilities: [
|
||||||
subscription = null;
|
TrackPlayer.CAPABILITY_PLAY,
|
||||||
|
TrackPlayer.CAPABILITY_PAUSE,
|
||||||
async componentDidMount() {
|
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
|
||||||
await TrackPlayer.setupPlayer();
|
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
|
||||||
await TrackPlayer.updateOptions({
|
TrackPlayer.CAPABILITY_STOP,
|
||||||
capabilities: [
|
TrackPlayer.CAPABILITY_SEEK_TO,
|
||||||
TrackPlayer.CAPABILITY_PLAY,
|
]
|
||||||
TrackPlayer.CAPABILITY_PAUSE,
|
});
|
||||||
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
|
|
||||||
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
|
|
||||||
TrackPlayer.CAPABILITY_STOP,
|
|
||||||
TrackPlayer.CAPABILITY_SEEK_TO,
|
|
||||||
]
|
|
||||||
});
|
|
||||||
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, colorScheme } = this.state;
|
|
||||||
|
|
||||||
if (!isReady) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
setupTrackPlayer();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistedStore}>
|
<PersistGate loading={null} persistor={persistedStore}>
|
||||||
<AppearanceProvider>
|
<ColorSchemeContext.Provider value={theme}>
|
||||||
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<Routes />
|
<Routes />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</AppearanceProvider>
|
</ColorSchemeContext.Provider>
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
70
src/components/Button.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { SvgProps } from 'react-native-svg';
|
||||||
|
import {
|
||||||
|
PressableProps, ViewProps,
|
||||||
|
} from 'react-native';
|
||||||
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import styled, { css } from 'styled-components/native';
|
||||||
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
|
interface ButtonProps extends PressableProps {
|
||||||
|
icon?: React.FC<SvgProps>;
|
||||||
|
title: string;
|
||||||
|
style?: ViewProps['style'];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PressableStyleProps {
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BaseButton = styled.Pressable<PressableStyleProps>`
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
${props => props.active && css`
|
||||||
|
background-color: ${THEME_COLOR};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonText = styled.Text<{ active?: boolean }>`
|
||||||
|
color: ${THEME_COLOR};
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
${props => props.active && css`
|
||||||
|
color: white;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function Button(props: ButtonProps) {
|
||||||
|
const { icon: Icon, title, ...rest } = props;
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
const [isPressed, setPressed] = useState(false);
|
||||||
|
const handlePressIn = useCallback(() => setPressed(true), []);
|
||||||
|
const handlePressOut = useCallback(() => setPressed(false), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseButton
|
||||||
|
{...rest}
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
active={isPressed}
|
||||||
|
style={[ defaultStyles.button, props.style ]}
|
||||||
|
>
|
||||||
|
{Icon &&
|
||||||
|
<Icon
|
||||||
|
width={12}
|
||||||
|
height={12}
|
||||||
|
fill={isPressed ? '#fff' : THEME_COLOR}
|
||||||
|
style={{
|
||||||
|
marginRight: 8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<ButtonText active={isPressed}>{title}</ButtonText>
|
||||||
|
</BaseButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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'),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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'),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
80
src/components/Colors.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import React from 'react';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { ColorSchemeName, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for generating both the dark and light stylesheets, so that they
|
||||||
|
* don't have to be generate on every individual component render
|
||||||
|
*/
|
||||||
|
function generateStyles(scheme: ColorSchemeName) {
|
||||||
|
return StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
color: scheme === 'dark' ? '#fff' : '#000',
|
||||||
|
},
|
||||||
|
textHalfOpacity: {
|
||||||
|
color: scheme === 'dark' ? '#ffffff88' : '#00000088',
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#111' : '#f6f6f6',
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
borderColor: scheme === 'dark' ? '#262626' : '#ddd',
|
||||||
|
},
|
||||||
|
activeBackground: {
|
||||||
|
backgroundColor: `${THEME_COLOR}${scheme === 'dark' ? '66' : '16'}`,
|
||||||
|
},
|
||||||
|
imageBackground: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#333' : '#ddd',
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#222222ee' : '#eeeeeeee',
|
||||||
|
},
|
||||||
|
modalInner: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#000' : '#fff',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#161616' : '#e6e6e6',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#161616' : '#e6e6e6',
|
||||||
|
color: scheme === 'dark' ? '#fff' : '#000',
|
||||||
|
},
|
||||||
|
sectionHeading: {
|
||||||
|
backgroundColor: scheme === 'dark' ? '#111' : '#eee',
|
||||||
|
borderColor: scheme === 'dark' ? '#333' : '#ddd',
|
||||||
|
},
|
||||||
|
stackHeader: {
|
||||||
|
color: scheme === 'dark' ? 'white' : 'black'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prerender both stylesheets
|
||||||
|
export const themes: Record<'dark' | 'light', ReturnType<typeof generateStyles>> = {
|
||||||
|
'dark': generateStyles('dark'),
|
||||||
|
'light': generateStyles('light'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create context for supplying the theming information
|
||||||
|
export const ColorSchemeContext = React.createContext(themes.dark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the default styles object in hook form
|
||||||
|
*/
|
||||||
|
export default function useDefaultStyles() {
|
||||||
|
return useContext(ColorSchemeContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultStylesProviderProps {
|
||||||
|
children: (defaultStyles: ReturnType<typeof useDefaultStyles>) => JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A render props component to supply the defaultStyles object.
|
||||||
|
*/
|
||||||
|
export function DefaultStylesProvider(props: DefaultStylesProviderProps) {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
|
return props.children(defaultStyles);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import styled from 'styled-components/native';
|
|||||||
|
|
||||||
const Input = styled.TextInput`
|
const Input = styled.TextInput`
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,48 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { TouchableOpacityProps, Text } from 'react-native';
|
import { TouchableOpacityProps } from 'react-native';
|
||||||
import ChevronRight from 'assets/chevron-right.svg';
|
import ChevronRight from 'assets/chevron-right.svg';
|
||||||
import styled from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from './Colors';
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
const BUTTON_SIZE = 14;
|
const BUTTON_SIZE = 14;
|
||||||
|
|
||||||
const Container = styled.TouchableOpacity`
|
const Container = styled.Pressable<{ active?: boolean }>`
|
||||||
padding: 18px 0;
|
padding: 18px 20px;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
${props => props.active && css`
|
||||||
|
background-color: ${THEME_COLOR};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Label = styled.Text<{ active?: boolean }>`
|
||||||
|
color: ${THEME_COLOR};
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
${props => props.active && css`
|
||||||
|
color: white;
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) => {
|
const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) => {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
const [isPressed, setPressed] = useState(false);
|
||||||
|
const handlePressIn = useCallback(() => setPressed(true), []);
|
||||||
|
const handlePressOut = useCallback(() => setPressed(false), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container {...props} style={colors.border}>
|
<Container
|
||||||
<Text style={{ color: THEME_COLOR, fontSize: 16 }}>{children}</Text>
|
{...props}
|
||||||
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={THEME_COLOR} />
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
style={defaultStyles.border}
|
||||||
|
active={isPressed}
|
||||||
|
>
|
||||||
|
<Label active={isPressed}>{children}</Label>
|
||||||
|
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={isPressed ? '#fff' : THEME_COLOR} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,39 @@
|
|||||||
import React from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import styled from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import { SafeAreaView } from 'react-native';
|
import { SafeAreaView, Pressable } from 'react-native';
|
||||||
import { colors } from './Colors';
|
import { useNavigation, StackActions } from '@react-navigation/native';
|
||||||
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
const Background = styled.View`
|
interface Props {
|
||||||
|
fullSize?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Background = styled(Pressable)`
|
||||||
padding: 100px 25px;
|
padding: 100px 25px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled(Pressable)<Pick<Props, 'fullSize'>>`
|
||||||
border-radius: 20px;
|
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 defaultStyles = useDefaultStyles();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const closeModal = useCallback(() => {
|
||||||
|
navigation.dispatch(StackActions.popToTop());
|
||||||
|
}, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Background style={colors.modal}>
|
<Background style={defaultStyles.modal} onPress={closeModal}>
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<Container style={colors.view}>
|
<Container style={defaultStyles.modalInner} fullSize={fullSize}>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|||||||
11
src/components/Text.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import { Text as BaseText, TextProps } from 'react-native';
|
||||||
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
|
export default function Text(props: PropsWithChildren<TextProps>) {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseText {...props} style={[defaultStyles.text, props.style]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,24 +1,41 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { Pressable, ViewStyle } from 'react-native';
|
||||||
|
|
||||||
interface TouchableHandlerProps {
|
interface TouchableHandlerProps {
|
||||||
id: string;
|
id: string;
|
||||||
onPress: (id: string) => void;
|
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
|
* 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.
|
* 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(() => {
|
const handlePress = useCallback(() => {
|
||||||
return onPress(id);
|
return onPress(id);
|
||||||
}, [id, onPress]);
|
}, [id, onPress]);
|
||||||
|
|
||||||
|
const handleLongPress = useCallback(() => {
|
||||||
|
return onLongPress ? onLongPress(id) : undefined;
|
||||||
|
}, [id, onLongPress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity onPress={handlePress}>
|
<Pressable
|
||||||
|
onPress={handlePress}
|
||||||
|
onLongPress={handleLongPress}
|
||||||
|
style={TouchableStyles}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</TouchableOpacity>
|
</Pressable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
|
import Text from './Text';
|
||||||
|
|
||||||
export const Header = styled.Text`
|
export const Header = styled(Text)`
|
||||||
margin: 24px 0 12px 0;
|
margin: 24px 0 12px 0;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SubHeader = styled(Text)`
|
||||||
|
font-size: 24px;
|
||||||
|
margin: 12px 0;
|
||||||
`;
|
`;
|
||||||
4
src/custom.d.ts
vendored
@@ -2,4 +2,8 @@ declare module '*.svg' {
|
|||||||
import { SvgProps } from 'react-native-svg';
|
import { SvgProps } from 'react-native-svg';
|
||||||
const content: React.FC<SvgProps>;
|
const content: React.FC<SvgProps>;
|
||||||
export default content;
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@env' {
|
||||||
|
export const SENTRY_DSN: string;
|
||||||
}
|
}
|
||||||
50
src/localisation/index.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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
|
||||||
|
let locale = findBestAvailableLanguage(Object.keys(localeGetters));
|
||||||
|
|
||||||
|
// Check if the locale is correctly picked
|
||||||
|
if (!locale || !locale.languageTag) {
|
||||||
|
// Some users might not list English as a fallback language, and hence might not
|
||||||
|
// be assigned a locale. In this case, we'll just default to English.
|
||||||
|
locale = {
|
||||||
|
languageTag: 'en',
|
||||||
|
isRTL: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
42
src/localisation/lang/en/locale.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"error-reporting": "Error Reporting",
|
||||||
|
"error-reporting-description": "During use of this app, you may encounter errors. Reporting these errors helps in creating a more secure and stable app experience.",
|
||||||
|
"error-reporting-rationale": "When you enable error reporting, every time an error occurs, a report is automatically created and sent to a server, along with helpful debugging information such as devices, versions and the specific error.",
|
||||||
|
"why-use-tracking": "Why use tracking?",
|
||||||
|
"why-use-tracking-description": "Tracking helps speed up development for this app by reporting weird edge cases and oversights. This helps make the app more stable and robust, thus increasing the app experience for everyone.",
|
||||||
|
"what-data-is-gathered": "What data is gathered?",
|
||||||
|
"what-data-is-gathered-description": "We log the error, device type, OS version, app version and device id. No application state is sent in any error reporting. The device id is a unique hash that can be reset in your device settings, and we cannot deduce any personal information from this identifier.",
|
||||||
|
"where-is-data-stored": "Where is data stored?",
|
||||||
|
"where-is-data-stored-description": "The Sentry backend is self-hosted on our own infrastructure. No-one but us has access to the servers, databases, application and data logs, least of all any Sentry staff. The infrastructure is hosted in the European Union.",
|
||||||
|
"enable-error-reporting": "Do you want to enable error reporting?",
|
||||||
|
"enable-error-reporting-description": "This helps improve the app experience by sending crash and error reports to us.",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable",
|
||||||
|
"more-info": "More Info"
|
||||||
|
}
|
||||||
28
src/localisation/lang/es/locale.json
Normal 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"
|
||||||
|
}
|
||||||
28
src/localisation/lang/fr/locale.json
Normal 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 l’intégralité de bibliothèque.",
|
||||||
|
"reset-cache": "Réinitialiser le cache",
|
||||||
|
"recent-albums": "Albums récents"
|
||||||
|
}
|
||||||
42
src/localisation/lang/nl/locale.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"error-reporting": "Foutmeldingen Rapporteren",
|
||||||
|
"error-reporting-description": "Gedurende het gebruik van deze app kun je tegen fouten aanlopen. Het rapporteren van deze foutmeldingen helpt om een veiligere en stabielere app-ervaring te maken.",
|
||||||
|
"error-reporting-rationale": "Wanneer je foutmeldingen aanzet, wordt er elke keer als je een foutmelding krijgt een automatisch rapport gegenereerd en naar onze server gestuurd. Deze bevat behulpzame informatie, zoals apparaat, versies en de specifieke foutmelding.",
|
||||||
|
"why-use-tracking": "Waarom tracking gebruiken?",
|
||||||
|
"why-use-tracking-description": "Tracking helpt het versnellen van het ontwikkeltempo doordat we beter inzicht hebben op rare gevallen en dingen die we gemist hebben. Dit helpt met het stabieler en veiliger maken van de voor iedereen.",
|
||||||
|
"what-data-is-gathered": "Welke data wordt er verzameld?",
|
||||||
|
"what-data-is-gathered-description": "We loggen de foutmeldingen, je apparaattype, versienummers en een device id. Geen applicatiestaat wordt gedeeld in de rapportage. Het device id is een unieke hash die gereset kan worden in de instellingen van je apparaat. Wij kunnen geen verdere informatie afleiden van deze identifier.",
|
||||||
|
"where-is-data-stored": "Waar wordt de data opgeslagen?",
|
||||||
|
"where-is-data-stored-description": "De Sentry backend wordt door onszelf gehost op onze eigen infrastructuur. Niemand behalve wij heeft toegang tot de servers, databases, applicaties en data logs, en zeker geen Sentry-werknemers. De infrastructuur wordt gehost in de Europese Unie.",
|
||||||
|
"enable-error-reporting": "Wil je foutmeldingen rapporteren?",
|
||||||
|
"enable-error-reporting-description": "Dit helpt de appervaring te verbeteren door ons rapportages te sturen van crashes en andere foutmeldingen.",
|
||||||
|
"enable": "Zet aan",
|
||||||
|
"disable": "Zet uit",
|
||||||
|
"more-info": "Meer informatie"
|
||||||
|
}
|
||||||
40
src/localisation/types.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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'
|
||||||
|
| 'error-reporting'
|
||||||
|
| 'error-reporting-description'
|
||||||
|
| 'error-reporting-rationale'
|
||||||
|
| 'why-use-tracking'
|
||||||
|
| 'why-use-tracking-description'
|
||||||
|
| 'what-data-is-gathered'
|
||||||
|
| 'what-data-is-gathered-description'
|
||||||
|
| 'where-is-data-stored'
|
||||||
|
| 'where-is-data-stored-description'
|
||||||
|
| 'enable-error-reporting'
|
||||||
|
| 'enable-error-reporting-description'
|
||||||
|
| 'enable'
|
||||||
|
| 'disable'
|
||||||
|
| 'more-info'
|
||||||
@@ -6,21 +6,23 @@ import Album from './stacks/Album';
|
|||||||
import RecentAlbums from './stacks/RecentAlbums';
|
import RecentAlbums from './stacks/RecentAlbums';
|
||||||
import Search from './stacks/Search';
|
import Search from './stacks/Search';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const Stack = createStackNavigator<StackParams>();
|
const Stack = createStackNavigator<StackParams>();
|
||||||
|
|
||||||
const navigationOptions = {
|
|
||||||
headerTintColor: THEME_COLOR,
|
|
||||||
headerTitleStyle: { color: 'black' }
|
|
||||||
};
|
|
||||||
|
|
||||||
function MusicStack() {
|
function MusicStack() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={navigationOptions}>
|
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={{
|
||||||
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: 'Recent Albums' }} />
|
headerTintColor: THEME_COLOR,
|
||||||
<Stack.Screen name="Albums" component={Albums} />
|
headerTitleStyle: defaultStyles.stackHeader
|
||||||
<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>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { StackParams } from '../types';
|
import { StackParams } from '../types';
|
||||||
import { Text, ScrollView, Dimensions, Button, RefreshControl, StyleSheet } from 'react-native';
|
import { Text, ScrollView, Dimensions, RefreshControl, StyleSheet, View } from 'react-native';
|
||||||
import { useGetImage } from 'utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import styled, { css } from 'styled-components/native';
|
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 FastImage from 'react-native-fast-image';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
@@ -11,10 +11,13 @@ import { useTypedSelector } from 'store';
|
|||||||
import { fetchTracksByAlbum } from 'store/music/actions';
|
import { fetchTracksByAlbum } from 'store/music/actions';
|
||||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS, THEME_COLOR } from 'CONSTANTS';
|
import { ALBUM_CACHE_AMOUNT_OF_DAYS, THEME_COLOR } from 'CONSTANTS';
|
||||||
import usePlayAlbum from 'utility/usePlayAlbum';
|
import usePlayAlbum from 'utility/usePlayAlbum';
|
||||||
import usePlayTrack from 'utility/usePlayTrack';
|
|
||||||
import TouchableHandler from 'components/TouchableHandler';
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import { colors } from 'components/Colors';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
import Play from 'assets/play.svg';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
type Route = RouteProp<StackParams, 'Album'>;
|
type Route = RouteProp<StackParams, 'Album'>;
|
||||||
|
|
||||||
@@ -22,18 +25,15 @@ const Screen = Dimensions.get('screen');
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
name: {
|
name: {
|
||||||
...colors.text,
|
|
||||||
fontSize: 36,
|
fontSize: 36,
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
...colors.text,
|
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
marginBottom: 24
|
marginBottom: 24
|
||||||
},
|
},
|
||||||
index: {
|
index: {
|
||||||
...colors.text,
|
|
||||||
width: 20,
|
width: 20,
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
marginRight: 5
|
marginRight: 5
|
||||||
@@ -60,6 +60,8 @@ const TrackContainer = styled.View<{isPlaying: boolean}>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Album: React.FC = () => {
|
const Album: React.FC = () => {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
// Retrieve state
|
// Retrieve state
|
||||||
const { params: { id } } = useRoute<Route>();
|
const { params: { id } } = useRoute<Route>();
|
||||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||||
@@ -71,11 +73,26 @@ const Album: React.FC = () => {
|
|||||||
const getImage = useGetImage();
|
const getImage = useGetImage();
|
||||||
const playAlbum = usePlayAlbum();
|
const playAlbum = usePlayAlbum();
|
||||||
const currentTrack = useCurrentTrack();
|
const currentTrack = useCurrentTrack();
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
// Setup callbacks
|
// Setup callbacks
|
||||||
const selectAlbum = useCallback(() => { playAlbum(id); }, [playAlbum, id]);
|
const selectAlbum = useCallback(() => { playAlbum(id); }, [playAlbum, id]);
|
||||||
const selectTrack = usePlayTrack();
|
|
||||||
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
|
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
|
// Retrieve album tracks on load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -96,20 +113,27 @@ const Album: React.FC = () => {
|
|||||||
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AlbumImage source={{ uri: getImage(album?.Id) }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: getImage(album?.Id) }} style={defaultStyles.imageBackground} />
|
||||||
<Text style={styles.name} >{album?.Name}</Text>
|
<Text style={[ defaultStyles.text, styles.name ]} >{album?.Name}</Text>
|
||||||
<Text style={styles.artist}>{album?.AlbumArtist}</Text>
|
<Text style={[ defaultStyles.text, styles.artist ]}>{album?.AlbumArtist}</Text>
|
||||||
<Button title="Play Album" onPress={selectAlbum} color={THEME_COLOR} />
|
<Button title={t('play-album')} icon={Play} onPress={selectAlbum} />
|
||||||
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
<View style={{ marginTop: 15 }}>
|
||||||
<TouchableHandler key={trackId} id={trackId} onPress={selectTrack}>
|
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
||||||
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={colors.border}>
|
<TouchableHandler
|
||||||
<Text style={styles.index}>
|
key={trackId}
|
||||||
{tracks[trackId]?.IndexNumber}
|
id={trackId}
|
||||||
</Text>
|
onPress={selectTrack}
|
||||||
<Text style={colors.text}>{tracks[trackId]?.Name}</Text>
|
onLongPress={longPressTrack}
|
||||||
</TrackContainer>
|
>
|
||||||
</TouchableHandler>
|
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={defaultStyles.border}>
|
||||||
) : undefined}
|
<Text style={[ defaultStyles.text, styles.index ]}>
|
||||||
|
{tracks[trackId]?.IndexNumber}
|
||||||
|
</Text>
|
||||||
|
<Text style={defaultStyles.text}>{tracks[trackId]?.Name}</Text>
|
||||||
|
</TrackContainer>
|
||||||
|
</TouchableHandler>
|
||||||
|
) : undefined}
|
||||||
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useRef, PureComponent, ReactText } from 'react';
|
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
||||||
import { useGetImage } from 'utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { Album, NavigationProp } from '../types';
|
import { Album, NavigationProp } from '../types';
|
||||||
import { Text, SafeAreaView, SectionList, View } from 'react-native';
|
import { Text, SafeAreaView, SectionList, View } from 'react-native';
|
||||||
@@ -9,13 +9,12 @@ import { useTypedSelector } from 'store';
|
|||||||
import { fetchAllAlbums } from 'store/music/actions';
|
import { fetchAllAlbums } from 'store/music/actions';
|
||||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||||
import TouchableHandler from 'components/TouchableHandler';
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
import ListContainer from './components/ListContainer';
|
|
||||||
import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
||||||
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
|
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
|
||||||
import AlphabetScroller from 'components/AlphabetScroller';
|
import AlphabetScroller from 'components/AlphabetScroller';
|
||||||
import { EntityId } from '@reduxjs/toolkit';
|
import { EntityId } from '@reduxjs/toolkit';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
interface VirtualizedItemInfo {
|
interface VirtualizedItemInfo {
|
||||||
section: SectionedId,
|
section: SectionedId,
|
||||||
@@ -51,19 +50,16 @@ const SectionText = styled.Text`
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const sectionStyles = { ...colors.view, ...colors.border };
|
const SectionHeading = React.memo(function SectionHeading(props: { label: string }) {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
const { label } = props;
|
||||||
|
|
||||||
class SectionHeading extends PureComponent<{ label: string }> {
|
return (
|
||||||
render() {
|
<SectionContainer style={defaultStyles.sectionHeading}>
|
||||||
const { label } = this.props;
|
<SectionText style={defaultStyles.text}>{label}</SectionText>
|
||||||
|
</SectionContainer>
|
||||||
return (
|
);
|
||||||
<SectionContainer style={sectionStyles}>
|
});
|
||||||
<SectionText style={colors.text}>{label}</SectionText>
|
|
||||||
</SectionContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeneratedAlbumItemProps {
|
interface GeneratedAlbumItemProps {
|
||||||
id: ReactText;
|
id: ReactText;
|
||||||
@@ -77,21 +73,20 @@ const HalfOpacity = styled.Text`
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
|
const GeneratedAlbumItem = React.memo(function GeneratedAlbumItem(props: GeneratedAlbumItemProps) {
|
||||||
render() {
|
const defaultStyles = useDefaultStyles();
|
||||||
const { id, imageUrl, name, artist, onPress } = this.props;
|
const { id, imageUrl, name, artist, onPress } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableHandler id={id as string} onPress={onPress}>
|
<TouchableHandler id={id as string} onPress={onPress}>
|
||||||
<AlbumItem>
|
<AlbumItem>
|
||||||
<AlbumImage source={{ uri: imageUrl }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: imageUrl }} style={defaultStyles.imageBackground} />
|
||||||
<Text numberOfLines={1} style={colors.text}>{name}</Text>
|
<Text numberOfLines={1} style={defaultStyles.text}>{name}</Text>
|
||||||
<HalfOpacity style={colors.text} numberOfLines={1}>{artist}</HalfOpacity>
|
<HalfOpacity style={defaultStyles.text} numberOfLines={1}>{artist}</HalfOpacity>
|
||||||
</AlbumItem>
|
</AlbumItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const Albums: React.FC = () => {
|
const Albums: React.FC = () => {
|
||||||
// Retrieve data from store
|
// Retrieve data from store
|
||||||
|
|||||||
@@ -12,30 +12,36 @@ import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
|||||||
import { useRecentAlbums } from 'store/music/selectors';
|
import { useRecentAlbums } from 'store/music/selectors';
|
||||||
import { Header } from 'components/Typography';
|
import { Header } from 'components/Typography';
|
||||||
import ListButton from 'components/ListButton';
|
import ListButton from 'components/ListButton';
|
||||||
import { colors } from 'components/Colors';
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
artist: {
|
columnWrapper: {
|
||||||
...colors.text,
|
paddingLeft: 10,
|
||||||
opacity: 0.5,
|
paddingRight: 10
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const NavigationHeader: React.FC = () => {
|
const NavigationHeader: React.FC = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
||||||
const handleSearchClick = useCallback(() => { navigation.navigate('Search'); }, [navigation]);
|
const handleSearchClick = useCallback(() => { navigation.navigate('Search'); }, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListContainer>
|
<>
|
||||||
<ListButton onPress={handleAllAlbumsClick}>All Albums</ListButton>
|
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
||||||
<ListButton onPress={handleSearchClick}>Search</ListButton>
|
<ListButton onPress={handleSearchClick}>{t('search')}</ListButton>
|
||||||
<Header style={colors.text}>Recent Albums</Header>
|
<ListContainer>
|
||||||
</ListContainer>
|
<Header style={defaultStyles.text}>{t('recent-albums')}</Header>
|
||||||
|
</ListContainer>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RecentAlbums: React.FC = () => {
|
const RecentAlbums: React.FC = () => {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
// Retrieve data from store
|
// Retrieve data from store
|
||||||
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
||||||
const recentAlbums = useRecentAlbums(24);
|
const recentAlbums = useRecentAlbums(24);
|
||||||
@@ -55,25 +61,24 @@ const RecentAlbums: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<ListContainer>
|
<FlatList
|
||||||
<FlatList
|
data={recentAlbums as string[]}
|
||||||
data={recentAlbums as string[]}
|
refreshing={isLoading}
|
||||||
refreshing={isLoading}
|
onRefresh={retrieveData}
|
||||||
onRefresh={retrieveData}
|
numColumns={2}
|
||||||
numColumns={2}
|
keyExtractor={d => d}
|
||||||
keyExtractor={d => d}
|
columnWrapperStyle={styles.columnWrapper}
|
||||||
ListHeaderComponent={NavigationHeader}
|
ListHeaderComponent={NavigationHeader}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<TouchableHandler id={item} onPress={selectAlbum}>
|
<TouchableHandler id={item} onPress={selectAlbum}>
|
||||||
<AlbumItem>
|
<AlbumItem>
|
||||||
<AlbumImage source={{ uri: getImage(item) }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: getImage(item) }} style={defaultStyles.imageBackground} />
|
||||||
<Text style={colors.text} numberOfLines={1}>{albums[item]?.Name}</Text>
|
<Text style={defaultStyles.text} numberOfLines={1}>{albums[item]?.Name}</Text>
|
||||||
<Text style={styles.artist} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
|
<Text style={defaultStyles.textHalfOpacity} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
|
||||||
</AlbumItem>
|
</AlbumItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</ListContainer>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import { useNavigation } from '@react-navigation/native';
|
|||||||
import { useGetImage } from 'utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { NavigationProp } from '../types';
|
import { NavigationProp } from '../types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { colors } from 'components/Colors';
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
@@ -46,6 +47,8 @@ const fuseOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
// Prepare state
|
// Prepare state
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const albums = useTypedSelector(state => state.music.albums.entities);
|
const albums = useTypedSelector(state => state.music.albums.entities);
|
||||||
@@ -88,10 +91,10 @@ export default function Search() {
|
|||||||
|
|
||||||
const HeaderComponent = React.useMemo(() => (
|
const HeaderComponent = React.useMemo(() => (
|
||||||
<Container>
|
<Container>
|
||||||
<Input value={searchTerm} onChangeText={setSearchTerm} style={colors.input} placeholder="Search..." />
|
<Input value={searchTerm} onChangeText={setSearchTerm} style={defaultStyles.input} placeholder={t('search') + '...'} />
|
||||||
{(searchTerm.length && !results.length) ? <Text>No results...</Text> : null}
|
{(searchTerm.length && !results.length) ? <Text style={{ textAlign: 'center' }}>{t('no-results')}</Text> : null}
|
||||||
</Container>
|
</Container>
|
||||||
), [searchTerm, results, setSearchTerm]);
|
), [searchTerm, results, setSearchTerm, defaultStyles]);
|
||||||
|
|
||||||
// GUARD: We cannot search for stuff unless Fuse is loaded with results.
|
// GUARD: We cannot search for stuff unless Fuse is loaded with results.
|
||||||
// Therefore we delay rendering to when we are certain it's there.
|
// Therefore we delay rendering to when we are certain it's there.
|
||||||
@@ -104,13 +107,13 @@ export default function Search() {
|
|||||||
data={results}
|
data={results}
|
||||||
renderItem={({ item: { item: album } }) =>(
|
renderItem={({ item: { item: album } }) =>(
|
||||||
<TouchableHandler id={album.Id} onPress={selectAlbum}>
|
<TouchableHandler id={album.Id} onPress={selectAlbum}>
|
||||||
<SearchResult style={colors.border}>
|
<SearchResult style={defaultStyles.border}>
|
||||||
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
||||||
<View>
|
<View>
|
||||||
<Text numberOfLines={1} ellipsizeMode="tail" style={colors.text}>
|
<Text numberOfLines={1} ellipsizeMode="tail" style={defaultStyles.text}>
|
||||||
{album.Name} - {album.AlbumArtist}
|
{album.Name} - {album.AlbumArtist}
|
||||||
</Text>
|
</Text>
|
||||||
<HalfOpacity style={colors.text}>Album</HalfOpacity>
|
<HalfOpacity style={defaultStyles.text}>{t('album')}</HalfOpacity>
|
||||||
</View>
|
</View>
|
||||||
</SearchResult>
|
</SearchResult>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export type StackParams = {
|
|||||||
Albums: undefined;
|
Albums: undefined;
|
||||||
Album: { id: string, album: Album };
|
Album: { id: string, album: Album };
|
||||||
RecentAlbums: undefined;
|
RecentAlbums: undefined;
|
||||||
|
Search: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavigationProp = StackNavigationProp<StackParams>;
|
export type NavigationProp = StackNavigationProp<StackParams>;
|
||||||
|
|||||||
77
src/screens/Onboarding/index.tsx
Normal 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;
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
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 TrackPlayer, { usePlaybackState, STATE_PLAYING, STATE_PAUSED } from 'react-native-track-player';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity, useColorScheme } from 'react-native';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { useHasQueue } from 'utility/useQueue';
|
import { useHasQueue } from 'utility/useQueue';
|
||||||
import ForwardIcon from 'assets/forwards.svg';
|
import ForwardIcon from 'assets/forwards.svg';
|
||||||
import BackwardIcon from 'assets/backwards.svg';
|
import BackwardIcon from 'assets/backwards.svg';
|
||||||
import PlayIcon from 'assets/play.svg';
|
import PlayIcon from 'assets/play.svg';
|
||||||
import PauseIcon from 'assets/pause.svg';
|
import PauseIcon from 'assets/pause.svg';
|
||||||
import { useColorScheme } from 'react-native-appearance';
|
import RepeatIcon from 'assets/repeat.svg';
|
||||||
|
// import ShuffleIcon from 'assets/shuffle.svg';
|
||||||
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
|
||||||
const BUTTON_SIZE = 40;
|
const BUTTON_SIZE = 40;
|
||||||
|
const BUTTON_SIZE_SMALL = 25;
|
||||||
|
|
||||||
const pause = () => TrackPlayer.pause();
|
const pause = () => TrackPlayer.pause();
|
||||||
const play = () => TrackPlayer.play();
|
const play = () => TrackPlayer.play();
|
||||||
@@ -47,6 +50,14 @@ export default function MediaControls() {
|
|||||||
<NextButton fill={fill} />
|
<NextButton fill={fill} />
|
||||||
</Button>
|
</Button>
|
||||||
</Buttons>
|
</Buttons>
|
||||||
|
<Buttons>
|
||||||
|
<Button>
|
||||||
|
<RepeatButton fill={fill} />
|
||||||
|
</Button>
|
||||||
|
<Button>
|
||||||
|
{/* <ShuffleButton fill={fill} /> */}
|
||||||
|
</Button>
|
||||||
|
</Buttons>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -71,6 +82,67 @@ export function NextButton({ fill }: { fill: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }) {
|
export function MainButton({ fill }: { fill: string }) {
|
||||||
const state = usePlaybackState();
|
const state = usePlaybackState();
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Dimensions, View, StyleSheet } from 'react-native';
|
import { Dimensions, View, StyleSheet } from 'react-native';
|
||||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
const Screen = Dimensions.get('screen');
|
const Screen = Dimensions.get('screen');
|
||||||
|
|
||||||
@@ -12,19 +13,15 @@ const Artwork = styled(FastImage)`
|
|||||||
width: ${Screen.width * 0.8}px;
|
width: ${Screen.width * 0.8}px;
|
||||||
height: ${Screen.width * 0.8}px;
|
height: ${Screen.width * 0.8}px;
|
||||||
margin: 25px auto;
|
margin: 25px auto;
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
artist: {
|
artist: {
|
||||||
...colors.text,
|
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
...colors.text,
|
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@@ -36,11 +33,18 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
export default function NowPlaying() {
|
export default function NowPlaying() {
|
||||||
const track = useCurrentTrack();
|
const track = useCurrentTrack();
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ alignItems: 'center' }}>
|
<View style={{ alignItems: 'center' }}>
|
||||||
<Artwork style={colors.imageBackground} source={{ uri: track?.artwork }} />
|
<Artwork
|
||||||
<Text style={styles.artist} >{track?.artist}</Text>
|
style={defaultStyles.imageBackground}
|
||||||
|
source={{
|
||||||
|
uri: track?.artwork,
|
||||||
|
priority: FastImage.priority.high,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text style={styles.artist}>{track?.artist}</Text>
|
||||||
<Text style={styles.title}>{track?.title}</Text>
|
<Text style={styles.title}>{track?.title}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import styled from 'styled-components/native';
|
|||||||
import { Text, Platform } from 'react-native';
|
import { Text, Platform } from 'react-native';
|
||||||
import Slider from '@react-native-community/slider';
|
import Slider from '@react-native-community/slider';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from 'components/Colors';
|
import { DefaultStylesProvider } from 'components/Colors';
|
||||||
|
|
||||||
const NumberBar = styled.View`
|
const NumberBar = styled.View`
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -65,24 +65,29 @@ export default class ProgressBar extends Component<{}, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { position, duration, gesture } = this.state;
|
const { position, duration, gesture } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DefaultStylesProvider>
|
||||||
<Slider
|
{defaultStyle => (
|
||||||
value={gesture || position}
|
<>
|
||||||
minimumValue={0}
|
<Slider
|
||||||
maximumValue={duration || 0}
|
value={gesture || position}
|
||||||
onValueChange={this.handleGesture}
|
minimumValue={0}
|
||||||
onSlidingComplete={this.handleEndOfGesture}
|
maximumValue={duration || 0}
|
||||||
minimumTrackTintColor={THEME_COLOR}
|
onValueChange={this.handleGesture}
|
||||||
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
|
onSlidingComplete={this.handleEndOfGesture}
|
||||||
disabled={!duration}
|
minimumTrackTintColor={THEME_COLOR}
|
||||||
/>
|
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
|
||||||
<NumberBar>
|
disabled={!duration}
|
||||||
<Text style={colors.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
|
/>
|
||||||
<Text style={colors.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
|
<NumberBar>
|
||||||
</NumberBar>
|
<Text style={defaultStyle.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
|
||||||
</>
|
<Text style={defaultStyle.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
|
||||||
|
</NumberBar>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</DefaultStylesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import useQueue from 'utility/useQueue';
|
import useQueue from 'utility/useQueue';
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, StyleSheet } from 'react-native';
|
||||||
import styled, { css } from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import TouchableHandler from 'components/TouchableHandler';
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { t } from '@localisation';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
|
||||||
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
|
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -23,18 +25,18 @@ const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDar
|
|||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const ClearQueue = styled.View`
|
||||||
|
margin: 20px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
title: {
|
trackTitle: {
|
||||||
...colors.text,
|
marginBottom: 2
|
||||||
marginBottom: 2,
|
|
||||||
},
|
|
||||||
artist: {
|
|
||||||
...colors.text,
|
|
||||||
opacity: 0.5,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Queue() {
|
export default function Queue() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
const queue = useQueue();
|
const queue = useQueue();
|
||||||
const currentTrack = useCurrentTrack();
|
const currentTrack = useCurrentTrack();
|
||||||
const currentIndex = queue.findIndex(d => d.id === currentTrack?.id);
|
const currentIndex = queue.findIndex(d => d.id === currentTrack?.id);
|
||||||
@@ -42,26 +44,32 @@ export default function Queue() {
|
|||||||
await TrackPlayer.skip(trackId);
|
await TrackPlayer.skip(trackId);
|
||||||
await TrackPlayer.play();
|
await TrackPlayer.play();
|
||||||
}, []);
|
}, []);
|
||||||
|
const clearQueue = useCallback(async () => {
|
||||||
|
await TrackPlayer.reset();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Text style={{ marginTop: 20, marginBottom: 20 }}>Queue</Text>
|
<Text style={{ marginTop: 20, marginBottom: 20 }}>{t('queue')}</Text>
|
||||||
{queue.map((track, i) => (
|
{queue.map((track, i) => (
|
||||||
<TouchableHandler id={track.id} onPress={playTrack} key={i}>
|
<TouchableHandler id={track.id} onPress={playTrack} key={i}>
|
||||||
<QueueItem
|
<QueueItem
|
||||||
active={currentTrack?.id === track.id}
|
active={currentTrack?.id === track.id}
|
||||||
key={i}
|
key={i}
|
||||||
alreadyPlayed={i < currentIndex}
|
alreadyPlayed={i < currentIndex}
|
||||||
style={{
|
style={[
|
||||||
...colors.border,
|
defaultStyles.border,
|
||||||
...currentTrack?.id === track.id ? colors.activeBackground : {},
|
currentTrack?.id === track.id ? defaultStyles.activeBackground : {},
|
||||||
}}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.title}>{track.title}</Text>
|
<Text style={styles.trackTitle}>{track.title}</Text>
|
||||||
<Text style={styles.artist}>{track.artist}</Text>
|
<Text style={defaultStyles.textHalfOpacity}>{track.artist}</Text>
|
||||||
</QueueItem>
|
</QueueItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
))}
|
))}
|
||||||
|
<ClearQueue>
|
||||||
|
<Button title={t('clear-queue')} onPress={clearQueue} />
|
||||||
|
</ClearQueue>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -4,20 +4,19 @@ import MediaControls from './components/MediaControls';
|
|||||||
import ProgressBar from './components/ProgressBar';
|
import ProgressBar from './components/ProgressBar';
|
||||||
import NowPlaying from './components/NowPlaying';
|
import NowPlaying from './components/NowPlaying';
|
||||||
import Queue from './components/Queue';
|
import Queue from './components/Queue';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outer: colors.view,
|
|
||||||
inner: {
|
inner: {
|
||||||
padding: 25,
|
padding: 25,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(JSON.stringify(styles));
|
|
||||||
|
|
||||||
export default function Player() {
|
export default function Player() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView contentContainerStyle={styles.inner} style={styles.outer}>
|
<ScrollView contentContainerStyle={styles.inner} style={defaultStyles.view}>
|
||||||
<NowPlaying />
|
<NowPlaying />
|
||||||
<MediaControls />
|
<MediaControls />
|
||||||
<ProgressBar />
|
<ProgressBar />
|
||||||
|
|||||||
34
src/screens/Settings/components/Cache.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import TrackPlayer from 'react-native-track-player';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import music from 'store/music';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
|
const ClearCache = styled(Button)`
|
||||||
|
margin-top: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.ScrollView`
|
||||||
|
padding: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Container>
|
||||||
|
<Text>{t('setting-cache-description')}</Text>
|
||||||
|
<ClearCache title={t('reset-cache')} onPress={handleClearCache} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
src/screens/Settings/components/Library.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import styled from 'styled-components/native';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import { NavigationProp } from '../..';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
|
const InputContainer = styled.View`
|
||||||
|
margin: 10px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Input = styled.TextInput`
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.ScrollView`
|
||||||
|
padding: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function LibrarySettings() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
const { jellyfin } = useTypedSelector(state => state.settings);
|
||||||
|
const navigation = useNavigation<NavigationProp>();
|
||||||
|
const handleSetLibrary = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<InputContainer>
|
||||||
|
<Text style={defaultStyles.text}>{t('jellyfin-server-url')}</Text>
|
||||||
|
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={defaultStyles.input} />
|
||||||
|
</InputContainer>
|
||||||
|
<InputContainer>
|
||||||
|
<Text style={defaultStyles.text}>{t('jellyfin-access-token')}</Text>
|
||||||
|
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={defaultStyles.input} />
|
||||||
|
</InputContainer>
|
||||||
|
<InputContainer>
|
||||||
|
<Text style={defaultStyles.text}>{t('jellyfin-user-id')}</Text>
|
||||||
|
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={defaultStyles.input} />
|
||||||
|
</InputContainer>
|
||||||
|
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
124
src/screens/Settings/components/Sentry.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import Text from 'components/Text';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Switch } from 'react-native-gesture-handler';
|
||||||
|
import styled, { css } from 'styled-components/native';
|
||||||
|
import { isSentryEnabled, setSentryStatus } from 'utility/Sentry';
|
||||||
|
import Accordion from 'react-native-collapsible/Accordion';
|
||||||
|
import ChevronIcon from 'assets/chevron-right.svg';
|
||||||
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
import useDefaultStyles, { DefaultStylesProvider } from 'components/Colors';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
|
||||||
|
const Container = styled.ScrollView`
|
||||||
|
padding: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SwitchContainer = styled.View`
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 16px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HeaderContainer = styled.View<{ isActive?: boolean }>`
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 16px 0 4px 0;
|
||||||
|
|
||||||
|
${props => props.isActive && css`
|
||||||
|
background-color: ${THEME_COLOR};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HeaderText = styled(Text)`
|
||||||
|
font-size: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ContentContainer = styled.View`
|
||||||
|
margin-top: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Label = styled(Text)`
|
||||||
|
font-size: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Chevron = styled(ChevronIcon)<{ isActive?: boolean }>`
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
|
||||||
|
${props => props.isActive && css`
|
||||||
|
transform: rotate(90deg);
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Question = { title: string, content: string };
|
||||||
|
|
||||||
|
const questions: Question[] = [
|
||||||
|
{
|
||||||
|
title: t('why-use-tracking'),
|
||||||
|
content: t('why-use-tracking-description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('what-data-is-gathered'),
|
||||||
|
content: t('what-data-is-gathered-description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('where-is-data-stored'),
|
||||||
|
content: t('where-is-data-stored-description')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function renderHeader(question: Question, index: number, isActive: boolean) {
|
||||||
|
return (
|
||||||
|
<HeaderContainer>
|
||||||
|
<HeaderText>{question.title}</HeaderText>
|
||||||
|
<DefaultStylesProvider>
|
||||||
|
{styles =>
|
||||||
|
<Chevron fill={styles.text.color} isActive={isActive} />
|
||||||
|
}
|
||||||
|
</DefaultStylesProvider>
|
||||||
|
</HeaderContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderContent(question: Question) {
|
||||||
|
return (
|
||||||
|
<ContentContainer>
|
||||||
|
<Text>{question.content}</Text>
|
||||||
|
</ContentContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Sentry() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
const [isReportingEnabled, setReporting] = useState(isSentryEnabled);
|
||||||
|
const [activeSections, setActiveSections] = useState<number[]>([]);
|
||||||
|
|
||||||
|
const toggleSwitch = () => setReporting(previousState => !previousState);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSentryStatus(isReportingEnabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Text>{t('error-reporting-description')}</Text>
|
||||||
|
<Text />
|
||||||
|
<Text>{t('error-reporting-rationale')}</Text>
|
||||||
|
<SwitchContainer>
|
||||||
|
<Label>{t('error-reporting')}</Label>
|
||||||
|
<Switch value={isReportingEnabled} onValueChange={toggleSwitch} />
|
||||||
|
</SwitchContainer>
|
||||||
|
<Accordion
|
||||||
|
sections={questions}
|
||||||
|
renderHeader={renderHeader}
|
||||||
|
renderContent={renderContent}
|
||||||
|
activeSections={activeSections}
|
||||||
|
onChange={setActiveSections}
|
||||||
|
underlayColor={defaultStyles.activeBackground.backgroundColor}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,58 +1,46 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { View, Text, SafeAreaView, Button } from 'react-native';
|
import { SafeAreaView, ScrollView } from 'react-native';
|
||||||
import { Picker } from '@react-native-community/picker';
|
import Library from './components/Library';
|
||||||
import { ScrollView } from 'react-native-gesture-handler';
|
import Cache from './components/Cache';
|
||||||
import styled from 'styled-components/native';
|
import useDefaultStyles from 'components/Colors';
|
||||||
import { useSelector } from 'react-redux';
|
import { t } from '@localisation';
|
||||||
import { AppState } from 'store';
|
import { createStackNavigator } from '@react-navigation/stack';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NavigationProp } from '..';
|
import ListButton from 'components/ListButton';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { Header } from 'components/Typography';
|
import Sentry from './components/Sentry';
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
|
|
||||||
const InputContainer = styled.View`
|
export function SettingsList() {
|
||||||
margin: 10px 0;
|
const navigation = useNavigation();
|
||||||
`;
|
const handleLibraryClick = useCallback(() => { navigation.navigate('Library'); }, [navigation]);
|
||||||
|
const handleCacheClick = useCallback(() => { navigation.navigate('Cache'); }, [navigation]);
|
||||||
const Input = styled.TextInput`
|
const handleSentryClick = useCallback(() => { navigation.navigate('Sentry'); }, [navigation]);
|
||||||
background-color: #fbfbfb;
|
|
||||||
padding: 15px;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default function Settings() {
|
|
||||||
const { jellyfin, bitrate } = useSelector((state: AppState) => state.settings);
|
|
||||||
const navigation = useNavigation<NavigationProp>();
|
|
||||||
const handleClick = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<View style={{ padding: 20 }}>
|
<ListButton onPress={handleLibraryClick}>{t('jellyfin-library')}</ListButton>
|
||||||
<Header style={colors.text}>Settings</Header>
|
<ListButton onPress={handleCacheClick}>{t('setting-cache')}</ListButton>
|
||||||
<InputContainer>
|
<ListButton onPress={handleSentryClick}>{t('error-reporting')}</ListButton>
|
||||||
<Text style={colors.text}>Jellyfin Server URL</Text>
|
|
||||||
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} />
|
|
||||||
</InputContainer>
|
|
||||||
<InputContainer>
|
|
||||||
<Text style={colors.text}>Jellyfin Access Token</Text>
|
|
||||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} />
|
|
||||||
</InputContainer>
|
|
||||||
<InputContainer>
|
|
||||||
<Text style={colors.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 style={colors.text}>Bitrate</Text>
|
|
||||||
<Picker selectedValue={bitrate}>
|
|
||||||
<Picker.Item label="320kbps" value={140000000} />
|
|
||||||
</Picker>
|
|
||||||
</InputContainer>
|
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Stack = createStackNavigator();
|
||||||
|
|
||||||
|
export default function Settings() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack.Navigator initialRouteName="SettingList" screenOptions={{
|
||||||
|
headerTintColor: THEME_COLOR,
|
||||||
|
headerTitleStyle: defaultStyles.stackHeader
|
||||||
|
}}>
|
||||||
|
<Stack.Screen name="SettingList" component={SettingsList} options={{ headerTitle: t('settings') }} />
|
||||||
|
<Stack.Screen name="Library" component={Library} options={{ headerTitle: t('jellyfin-library') }} />
|
||||||
|
<Stack.Screen name="Cache" component={Cache} options={{ headerTitle: t('setting-cache') }} />
|
||||||
|
<Stack.Screen name="Sentry" component={Sentry} options={{ headerTitle: t('error-reporting') }} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,15 @@ import PlayPauseIcon from 'assets/play-pause-fill.svg';
|
|||||||
import NotesIcon from 'assets/notes.svg';
|
import NotesIcon from 'assets/notes.svg';
|
||||||
import GearIcon from 'assets/gear.svg';
|
import GearIcon from 'assets/gear.svg';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
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';
|
||||||
|
import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
||||||
|
import ErrorReportingPopup from './modals/ErrorReportingPopup';
|
||||||
|
|
||||||
const Stack = createStackNavigator();
|
const Stack = createStackNavigator<ModalStackParams>();
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
|
|
||||||
type Screens = {
|
type Screens = {
|
||||||
@@ -34,28 +41,39 @@ function getIcon(route: string): React.FC<any> | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Screens() {
|
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 (
|
return (
|
||||||
<Tab.Navigator
|
<>
|
||||||
screenOptions={({ route }) => ({
|
<ErrorReportingAlert />
|
||||||
tabBarIcon: function TabBarIcon({ color, size }) {
|
<Tab.Navigator
|
||||||
const Icon = getIcon(route.name);
|
screenOptions={({ route }) => ({
|
||||||
|
tabBarIcon: function TabBarIcon({ color, size }) {
|
||||||
|
const Icon = getIcon(route.name);
|
||||||
|
|
||||||
if (!Icon) {
|
if (!Icon) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Icon fill={color} width={size} height={size} />;
|
||||||
}
|
}
|
||||||
|
})}
|
||||||
return <Icon fill={color} width={size} height={size} />;
|
tabBarOptions={{
|
||||||
}
|
activeTintColor: THEME_COLOR,
|
||||||
})}
|
inactiveTintColor: 'gray',
|
||||||
tabBarOptions={{
|
}}
|
||||||
activeTintColor: THEME_COLOR,
|
>
|
||||||
inactiveTintColor: 'gray',
|
<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.Screen name="NowPlaying" component={Player} options={{ tabBarLabel: 'Now Playing' }} />
|
</Tab.Navigator>
|
||||||
<Tab.Screen name="Music" component={Music} />
|
</>
|
||||||
<Tab.Screen name="Settings" component={Settings} />
|
|
||||||
</Tab.Navigator>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +92,13 @@ export default function Routes() {
|
|||||||
}}>
|
}}>
|
||||||
<Stack.Screen name="Screens" component={Screens} />
|
<Stack.Screen name="Screens" component={Screens} />
|
||||||
<Stack.Screen name="SetJellyfinServer" component={SetJellyfinServer} />
|
<Stack.Screen name="SetJellyfinServer" component={SetJellyfinServer} />
|
||||||
|
<Stack.Screen name="TrackPopupMenu" component={TrackPopupMenu} />
|
||||||
|
<Stack.Screen name="ErrorReporting" component={ErrorReportingPopup} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NavigationProp = CompositeNavigationProp<
|
export type NavigationProp = CompositeNavigationProp<
|
||||||
StackNavigationProp<Routes>,
|
StackNavigationProp<Routes>,
|
||||||
BottomTabNavigationProp<Screens>
|
BottomTabNavigationProp<Screens>
|
||||||
>;
|
>;
|
||||||
11
src/screens/modals/ErrorReportingPopup.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import Sentry from 'screens/Settings/components/Sentry';
|
||||||
|
|
||||||
|
export default function ErrorReportingPopup() {
|
||||||
|
return (
|
||||||
|
<Modal fullSize={false}>
|
||||||
|
<Sentry />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import { Text, Button, View } from 'react-native';
|
import { Button, View } from 'react-native';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import Input from 'components/Input';
|
import Input from 'components/Input';
|
||||||
import { setJellyfinCredentials } from 'store/settings/actions';
|
import { setJellyfinCredentials } from 'store/settings/actions';
|
||||||
@@ -7,9 +7,12 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useNavigation, StackActions } from '@react-navigation/native';
|
import { useNavigation, StackActions } from '@react-navigation/native';
|
||||||
import CredentialGenerator from './components/CredentialGenerator';
|
import CredentialGenerator from './components/CredentialGenerator';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from 'components/Colors';
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
export default function SetJellyfinServer() {
|
export default function SetJellyfinServer() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
// State for first screen
|
// State for first screen
|
||||||
const [serverUrl, setServerUrl] = useState<string>();
|
const [serverUrl, setServerUrl] = useState<string>();
|
||||||
const [isLogginIn, setIsLogginIn] = useState<boolean>(false);
|
const [isLogginIn, setIsLogginIn] = useState<boolean>(false);
|
||||||
@@ -32,8 +35,10 @@ export default function SetJellyfinServer() {
|
|||||||
onCredentialsRetrieved={saveCredentials}
|
onCredentialsRetrieved={saveCredentials}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View style={{ padding: 20 }}>
|
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
<Text style={colors.text}>Please enter your Jellyfin server URL first. Make sure to include the protocol and port</Text>
|
<Text>
|
||||||
|
{t('set-jellyfin-server-instruction')}
|
||||||
|
</Text>
|
||||||
<Input
|
<Input
|
||||||
placeholder="https://jellyfin.yourserver.io/"
|
placeholder="https://jellyfin.yourserver.io/"
|
||||||
onChangeText={setServerUrl}
|
onChangeText={setServerUrl}
|
||||||
@@ -41,10 +46,10 @@ export default function SetJellyfinServer() {
|
|||||||
keyboardType="url"
|
keyboardType="url"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
style={colors.input}
|
style={[ defaultStyles.input, { width: '100%' } ]}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title="Set server"
|
title={t('set-jellyfin-server')}
|
||||||
onPress={() => setIsLogginIn(true)}
|
onPress={() => setIsLogginIn(true)}
|
||||||
disabled={!serverUrl?.length}
|
disabled={!serverUrl?.length}
|
||||||
color={THEME_COLOR}
|
color={THEME_COLOR}
|
||||||
|
|||||||
67
src/screens/modals/TrackPopupMenu.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import { useNavigation, StackActions, useRoute, RouteProp } from '@react-navigation/native';
|
||||||
|
import { ModalStackParams } from 'screens/types';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { SubHeader } from 'components/Typography';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
import usePlayTrack from 'utility/usePlayTrack';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
import PlayIcon from 'assets/play.svg';
|
||||||
|
import QueueAppendIcon from 'assets/queue-append.svg';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
|
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; */
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonSpacing = styled.View`
|
||||||
|
width: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
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')} icon={PlayIcon} onPress={handlePlayNext} />
|
||||||
|
<ButtonSpacing />
|
||||||
|
<Button title={t('add-to-queue')} icon={QueueAppendIcon} onPress={handleAddToQueue} />
|
||||||
|
</Buttons>
|
||||||
|
</Container>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TrackPopupMenu;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ModalStackParams {
|
||||||
|
SetJellyfinServer: undefined;
|
||||||
|
TrackPopupMenu: { trackId: string };
|
||||||
|
}
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
import { configureStore, getDefaultMiddleware, combineReducers } from '@reduxjs/toolkit';
|
import { configureStore, getDefaultMiddleware, combineReducers } from '@reduxjs/toolkit';
|
||||||
import { useSelector, TypedUseSelectorHook } from 'react-redux';
|
import { useSelector, TypedUseSelectorHook } from 'react-redux';
|
||||||
import AsyncStorage from '@react-native-community/async-storage';
|
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';
|
// import logger from 'redux-logger';
|
||||||
|
|
||||||
const persistConfig = {
|
const persistConfig: PersistConfig<AppState> = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
storage: AsyncStorage,
|
storage: AsyncStorage,
|
||||||
|
stateReconciler: autoMergeLevel2
|
||||||
};
|
};
|
||||||
|
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
import music from './music';
|
import music from './music';
|
||||||
|
import player from './player';
|
||||||
|
|
||||||
const reducers = combineReducers({
|
const reducers = combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
player: player.reducer,
|
||||||
music: music.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 AppDispatch = typeof store.dispatch;
|
||||||
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
export type AsyncThunkAPI = { state: AppState, dispatch: AppDispatch };
|
||||||
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
export const useTypedSelector: TypedUseSelectorHook<AppState> = useSelector;
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ const initialState: State = {
|
|||||||
const music = createSlice({
|
const music = createSlice({
|
||||||
name: 'music',
|
name: 'music',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {},
|
reducers: {
|
||||||
|
reset: () => initialState,
|
||||||
|
},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
/**
|
/**
|
||||||
* Fetch All albums
|
* Fetch All albums
|
||||||
@@ -58,6 +60,10 @@ const music = createSlice({
|
|||||||
* Fetch tracks by album
|
* Fetch tracks by album
|
||||||
*/
|
*/
|
||||||
builder.addCase(fetchTracksByAlbum.fulfilled, (state, { payload }) => {
|
builder.addCase(fetchTracksByAlbum.fulfilled, (state, { payload }) => {
|
||||||
|
if (!payload.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
trackAdapter.upsertMany(state.tracks, payload);
|
trackAdapter.upsertMany(state.tracks, payload);
|
||||||
|
|
||||||
// Also store all the track ids in the album
|
// Also store all the track ids in the album
|
||||||
|
|||||||
11
src/store/player/index.ts
Normal 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;
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
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 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');
|
||||||
|
export const setReceivedErrorReportingAlert = createAction<void>('SET_RECEIVED_ERROR_REPORTING_ALERT');
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createReducer } from '@reduxjs/toolkit';
|
import { createReducer } from '@reduxjs/toolkit';
|
||||||
import { setBitrate, setJellyfinCredentials } from './actions';
|
import { setReceivedErrorReportingAlert, setBitrate, setJellyfinCredentials, setOnboardingStatus } from './actions';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
jellyfin?: {
|
jellyfin?: {
|
||||||
@@ -9,10 +9,14 @@ interface State {
|
|||||||
device_id: string;
|
device_id: string;
|
||||||
}
|
}
|
||||||
bitrate: number;
|
bitrate: number;
|
||||||
|
isOnboardingComplete: boolean;
|
||||||
|
hasReceivedErrorReportingAlert: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: State = {
|
const initialState: State = {
|
||||||
bitrate: 140000000
|
bitrate: 140000000,
|
||||||
|
isOnboardingComplete: false,
|
||||||
|
hasReceivedErrorReportingAlert: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const settings = createReducer(initialState, {
|
const settings = createReducer(initialState, {
|
||||||
@@ -24,6 +28,14 @@ const settings = createReducer(initialState, {
|
|||||||
...state,
|
...state,
|
||||||
bitrate: action.payload,
|
bitrate: action.payload,
|
||||||
}),
|
}),
|
||||||
|
[setOnboardingStatus.type]: (state, action) => ({
|
||||||
|
...state,
|
||||||
|
isOnboardingComplete: action.payload,
|
||||||
|
}),
|
||||||
|
[setReceivedErrorReportingAlert.type]: (state) => ({
|
||||||
|
...state,
|
||||||
|
hasReceivedErrorReportingAlert: true,
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export default settings;
|
export default settings;
|
||||||
59
src/utility/ErrorReportingAlert.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Alert } from 'react-native';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
import { t } from '@localisation';
|
||||||
|
import { setReceivedErrorReportingAlert } from 'store/settings/actions';
|
||||||
|
import { setSentryStatus } from './Sentry';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will send out an alert message asking the user if they want to enable
|
||||||
|
* error reporting.
|
||||||
|
*/
|
||||||
|
export default function ErrorReportingAlert() {
|
||||||
|
const { hasReceivedErrorReportingAlert } = useTypedSelector(state => state.settings);
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Only send out alert if we haven't done so ever
|
||||||
|
if (!hasReceivedErrorReportingAlert) {
|
||||||
|
// Generate the alert
|
||||||
|
Alert.alert(
|
||||||
|
t('enable-error-reporting'),
|
||||||
|
t('enable-error-reporting-description'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: t('enable'),
|
||||||
|
style: 'default',
|
||||||
|
onPress: () => {
|
||||||
|
setSentryStatus(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('disable'),
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => {
|
||||||
|
setSentryStatus(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('more-info'),
|
||||||
|
style: 'cancel',
|
||||||
|
onPress: () => {
|
||||||
|
navigation.navigate('ErrorReporting');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store the flag that we have sent out the alert, so that we don't
|
||||||
|
// have to do so anymore in the future.
|
||||||
|
dispatch(setReceivedErrorReportingAlert());
|
||||||
|
}
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ function generateConfig(credentials: Credentials): RequestInit {
|
|||||||
const baseTrackOptions: Record<string, string> = {
|
const baseTrackOptions: Record<string, string> = {
|
||||||
// Not sure where this number refers to, but setting it to 140000000 appears
|
// Not sure where this number refers to, but setting it to 140000000 appears
|
||||||
// to do wonders for making stuff work
|
// 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',
|
MaxStreamingBitrate: '140000000',
|
||||||
MaxSampleRate: '48000',
|
MaxSampleRate: '48000',
|
||||||
// This must be set to support client seeking
|
// 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
|
// 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
|
// soon: https://github.com/react-native-kit/react-native-track-player/pull/950
|
||||||
// Container: 'mp3',
|
// 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',
|
AudioCodec: 'aac',
|
||||||
static: 'true',
|
static: 'true',
|
||||||
};
|
};
|
||||||
@@ -43,7 +45,7 @@ export function generateTrack(track: AlbumTrack, credentials: Credentials): Trac
|
|||||||
DeviceId: credentials?.device_id || '',
|
DeviceId: credentials?.device_id || '',
|
||||||
};
|
};
|
||||||
const trackParams = new URLSearchParams(trackOptions).toString();
|
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 {
|
return {
|
||||||
id: track.Id,
|
id: track.Id,
|
||||||
|
|||||||
50
src/utility/Sentry.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { SENTRY_DSN } from '@env';
|
||||||
|
import AsyncStorage from '@react-native-community/async-storage';
|
||||||
|
import * as Sentry from '@sentry/react-native';
|
||||||
|
|
||||||
|
const SENTRY_ASYNC__ITEM_STRING = 'sentry_enabled';
|
||||||
|
export let isSentryEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup Sentry based on what value is stored in AsyncStorage.
|
||||||
|
*/
|
||||||
|
export async function setupSentry(): Promise<void> {
|
||||||
|
// First, we'll retrieve the user settings. This delays Sentry being active
|
||||||
|
// slightly, at the bonus of acutally being able to send off reports for
|
||||||
|
// start-up stuff.
|
||||||
|
isSentryEnabled = (await AsyncStorage.getItem(SENTRY_ASYNC__ITEM_STRING)) === 'true';
|
||||||
|
|
||||||
|
// Make sure the DSN is actually set, in order to prevent weird erros.
|
||||||
|
if (SENTRY_DSN) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: SENTRY_DSN,
|
||||||
|
// Before we send any event, check whether the Sentry SDK should be
|
||||||
|
// enabled based on user settings.
|
||||||
|
beforeSend(event) {
|
||||||
|
// If so, pass off the event to the back-end
|
||||||
|
if (isSentryEnabled) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, don't sent a thing
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to enable or disable the Sentry SDK for this app.
|
||||||
|
*/
|
||||||
|
export async function setSentryStatus(isEnabled: boolean): Promise<void> {
|
||||||
|
// GUARD: If nothing's changed, change nothing
|
||||||
|
if (isEnabled === isSentryEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, store the value in Async Storage
|
||||||
|
await AsyncStorage.setItem(SENTRY_ASYNC__ITEM_STRING, isEnabled.toString());
|
||||||
|
|
||||||
|
// Then, assign it to the variable
|
||||||
|
isSentryEnabled = isEnabled;
|
||||||
|
}
|
||||||
@@ -14,15 +14,29 @@ export default function useCurrentTrack(): Track | undefined {
|
|||||||
|
|
||||||
// GUARD: Only fetch current track if there is a current track
|
// GUARD: Only fetch current track if there is a current track
|
||||||
if (!currentTrackId) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTrack = await TrackPlayer.getTrack(currentTrackId);
|
// If it is different, retrieve the track and save it
|
||||||
setTrack(currentTrack);
|
try {
|
||||||
|
const currentTrack = await TrackPlayer.getTrack(currentTrackId);
|
||||||
|
setTrack(currentTrack);
|
||||||
|
} catch {
|
||||||
|
// Due to the async nature, a track might be removed at the
|
||||||
|
// point when we try to retrieve it. If this happens, we'll just
|
||||||
|
// smother the error and wait for a new track update to
|
||||||
|
// finish.
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchTrack();
|
fetchTrack();
|
||||||
}, [state]);
|
}, [state, track, setTrack]);
|
||||||
|
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,22 @@ import { useTypedSelector } from 'store';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import TrackPlayer, { Track } from 'react-native-track-player';
|
import TrackPlayer, { Track } from 'react-native-track-player';
|
||||||
import { generateTrack } from './JellyfinApi';
|
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
|
* Generate a callback function that starts playing a full album given its
|
||||||
* supplied id.
|
* supplied id.
|
||||||
*/
|
*/
|
||||||
export default function usePlayAlbum() {
|
export default function usePlayAlbum() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
||||||
const albums = useTypedSelector(state => state.music.albums.entities);
|
const albums = useTypedSelector(state => state.music.albums.entities);
|
||||||
const tracks = useTypedSelector(state => state.music.tracks.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 album = albums[albumId];
|
||||||
const trackIds = album?.Tracks;
|
const trackIds = album?.Tracks;
|
||||||
|
|
||||||
@@ -21,6 +26,29 @@ export default function usePlayAlbum() {
|
|||||||
return;
|
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
|
// Convert all trackIds to the relevant format for react-native-track-player
|
||||||
const newTracks = trackIds.map((trackId) => {
|
const newTracks = trackIds.map((trackId) => {
|
||||||
const track = tracks[trackId];
|
const track = tracks[trackId];
|
||||||
@@ -34,7 +62,15 @@ export default function usePlayAlbum() {
|
|||||||
// Clear the queue and add all tracks
|
// Clear the queue and add all tracks
|
||||||
await TrackPlayer.removeUpcomingTracks();
|
await TrackPlayer.removeUpcomingTracks();
|
||||||
await TrackPlayer.add(newTracks);
|
await TrackPlayer.add(newTracks);
|
||||||
await TrackPlayer.skip(trackIds[0]);
|
|
||||||
TrackPlayer.play();
|
// Then, we'll dispatch the added track event
|
||||||
}, [credentials, albums, tracks]);
|
dispatch(player.actions.addNewTrackToPlayer());
|
||||||
|
|
||||||
|
if (play) {
|
||||||
|
await TrackPlayer.skip(trackIds[0]);
|
||||||
|
await TrackPlayer.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTracks;
|
||||||
|
}, [credentials, albums, tracks, queue, dispatch]);
|
||||||
}
|
}
|
||||||
@@ -3,17 +3,20 @@ import TrackPlayer from 'react-native-track-player';
|
|||||||
import { useTypedSelector } from 'store';
|
import { useTypedSelector } from 'store';
|
||||||
import { generateTrack } from './JellyfinApi';
|
import { generateTrack } from './JellyfinApi';
|
||||||
import useQueue from './useQueue';
|
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
|
* A hook that generates a callback that can setup and start playing a
|
||||||
* particular trackId in the player.
|
* particular trackId in the player.
|
||||||
*/
|
*/
|
||||||
export default function usePlayTrack() {
|
export default function usePlayTrack() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
const credentials = useTypedSelector(state => state.settings.jellyfin);
|
||||||
const tracks = useTypedSelector(state => state.music.tracks.entities);
|
const tracks = useTypedSelector(state => state.music.tracks.entities);
|
||||||
const queue = useQueue();
|
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
|
// Get the relevant track
|
||||||
const track = tracks[trackId];
|
const track = tracks[trackId];
|
||||||
|
|
||||||
@@ -30,10 +33,35 @@ export default function usePlayTrack() {
|
|||||||
...(trackInstances.length ? trackInstances[0] : generateTrack(track, credentials)),
|
...(trackInstances.length ? trackInstances[0] : generateTrack(track, credentials)),
|
||||||
id: `${trackId}_${trackInstances.length}`
|
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
|
// Then we'll skip to it and play it
|
||||||
await TrackPlayer.skip(newTrack.id);
|
if (play) {
|
||||||
TrackPlayer.play();
|
await TrackPlayer.skip(newTrack.id);
|
||||||
}, [credentials, tracks, queue]);
|
await TrackPlayer.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
return newTrack;
|
||||||
|
}, [credentials, tracks, queue, dispatch]);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import TrackPlayer, { usePlaybackState, Track } from 'react-native-track-player';
|
import TrackPlayer, { usePlaybackState, Track } from 'react-native-track-player';
|
||||||
|
import { useTypedSelector } from 'store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This hook retrieves the current playing track from TrackPlayer
|
* 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[] {
|
export default function useQueue(): Track[] {
|
||||||
const state = usePlaybackState();
|
const state = usePlaybackState();
|
||||||
const [queue, setQueue] = useState<Track[]>([]);
|
const [queue, setQueue] = useState<Track[]>([]);
|
||||||
|
const addedTrackCount = useTypedSelector(state => state.player);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
TrackPlayer.getQueue().then(setQueue);
|
TrackPlayer.getQueue().then(setQueue);
|
||||||
}, [state]);
|
}, [state, addedTrackCount]);
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,9 @@
|
|||||||
/* Module Resolution Options */
|
/* Module Resolution Options */
|
||||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
"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. */
|
"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. */
|
// "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. */
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
// "types": [], /* Type declaration files to be included in compilation. */
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
|||||||