Compare commits

...

147 Commits

Author SHA1 Message Date
Lei Nelissen
1402ac06f6 Bump major version 2022-01-16 12:37:09 +01:00
Lei Nelissen
f9334c51a3 Release new versions 2022-01-15 17:51:15 +01:00
Lei Nelissen
5d26a5395b Install JSON plugin for fastlane 2022-01-15 17:35:28 +01:00
Lei Nelissen
714535feeb Bump version 2022-01-15 17:26:07 +01:00
Lei Nelissen
7ea4857997 Queue downloads seperately so we don't overwhelm the app 2022-01-15 17:25:24 +01:00
Lei Nelissen
81ccb6b1f9 Use package.json for app version 2022-01-15 17:24:56 +01:00
Lei Nelissen
98ae0216f7 Replace icons with filled version 2022-01-15 17:24:47 +01:00
Lei Nelissen
55961d5530 Release new version 2022-01-03 09:08:00 +01:00
Lei Nelissen
7c32fb3599 Release new builds 2022-01-03 09:07:38 +01:00
Lei Nelissen
56971a9291 Add migrations for the store 2022-01-03 09:07:30 +01:00
Lei Nelissen
9bf20b1762 Release new version 2022-01-02 23:06:27 +01:00
Lei Nelissen
76598b38cb Add missing Dutch strings 2022-01-02 23:06:10 +01:00
Lei Nelissen
5d6f65b699 Add offline message as i18n string 2022-01-02 23:03:15 +01:00
Lei Nelissen
f78db52e0a Fix Dark Mode colors in new features 2022-01-02 23:02:54 +01:00
Lei Nelissen
611cbc8c69 Add connection notice 2022-01-02 22:50:49 +01:00
Lei Nelissen
cab3935a92 Update XCode so it builds 2022-01-02 22:50:38 +01:00
Lei Nelissen
f8e57827f2 Remove RNFS handler as it breaks the build 2022-01-02 22:50:19 +01:00
Lei Nelissen
55494caf4f Fix Android issues 2022-01-02 21:14:05 +01:00
Lei Nelissen
b7d16a174b Release new version 2022-01-02 19:30:54 +01:00
Lei Nelissen
3c98bf92eb Merge pull request #67 from leinelissen/feature/downloads
Downloads
2022-01-02 19:30:08 +01:00
Lei Nelissen
ebd46970d9 Fix linting errors 2022-01-02 19:29:20 +01:00
Lei Nelissen
cc0dfc2528 Allow for retring individual tracks 2022-01-02 19:16:12 +01:00
Lei Nelissen
74ddc58f83 Allow retries for failed tracks 2022-01-02 18:11:30 +01:00
Lei Nelissen
95a008d8af Add buttons for deleting downloaded tracks from playlist or album 2022-01-02 17:46:50 +01:00
Lei Nelissen
db1c9d8af9 Adjust transport container so we can actually download non-support audio files 2022-01-02 17:21:24 +01:00
Lei Nelissen
c28c1a851d Only render search message if nothing is found 2022-01-02 17:21:02 +01:00
Lei Nelissen
d2fb4a4aea Basic download implementation 2022-01-02 02:28:52 +01:00
Lei Nelissen
284d23d30b Merge pull request #66 from leinelissen/feature/playlists
Add playlists
2022-01-01 22:58:47 +01:00
Lei Nelissen
464747d0c4 Limit tsc output to our files rather than libraries 2022-01-01 22:44:15 +01:00
Lei Nelissen
419ce99d08 Add linting to GitHub actions 2022-01-01 22:37:58 +01:00
Lei Nelissen
4e6a758d83 Fix linting and typescript errors 2022-01-01 22:36:05 +01:00
Lei Nelissen
4ecf978505 Add Dutch translations 2022-01-01 21:58:06 +01:00
Lei Nelissen
5d96a67336 Make playlist view full-height 2022-01-01 21:57:18 +01:00
Lei Nelissen
6a4b5618aa Resolve track pop-up spacing 2022-01-01 21:55:32 +01:00
Lei Nelissen
75e8ece60a Add playlists 2022-01-01 19:09:21 +01:00
Lei Nelissen
4460bdf7f9 (1) Automatically focus on text field in search
(2) Restyle no results message
2022-01-01 14:27:08 +01:00
Lei Nelissen
9b41a0e62f Contextually disable previous and next buttons 2022-01-01 14:10:56 +01:00
Lei Nelissen
93c9ba7498 Adjust modal display with new react-navigation look 2022-01-01 14:09:52 +01:00
Lei Nelissen
9668ba9c7c Upgrade all dependencies
(1) react-native-track-player to v2
(2) react-navigation to v6
(3) react-native to v0.66.4
2021-12-31 15:04:37 +01:00
Lei Nelissen
6440c1ac7b Bump version 2021-10-25 23:50:33 +02:00
Lei Nelissen
6c0a277397 Update dependencies 2021-10-25 23:36:14 +02:00
Lei Nelissen
080a664d4f Merge pull request #58 from Sjord/nl-locale-fixes
Dutch translation fixes
2021-10-25 22:25:22 +02:00
Sjoerd Langkemper
ae6a99ce6d Dutch translation fixes
achtergrond = background
instellen, stel ... in = to configure
2021-10-12 21:26:02 +02:00
Lei Nelissen
b14f546525 Add Japanese as a locale 2021-05-12 22:18:23 +02:00
Lei Nelissen
2b8e78a076 Merge pull request #46 from leinelissen/translations_src-localisation-lang-en-locale-json--master_ja
Translate '/src/localisation/lang/en/locale.json' in 'ja'
2021-05-12 22:14:56 +02:00
Lei Nelissen
4a345699a8 Fix search box (fix #47) 2021-05-12 22:13:44 +02:00
Lei Nelissen
eaef9be7db Update dependencies 2021-05-12 22:03:29 +02:00
transifex-integration[bot]
8049a25cce Apply translations in ja
translation completed for the source file '/src/localisation/lang/en/locale.json'
on the 'ja' language.
2021-05-12 14:42:54 +00:00
Lei Nelissen
7403ab986b Release build 16 2021-04-25 23:45:20 +02:00
Lei Nelissen
d8e9f8f4c6 Allow arbitrary loads (ie HTTP) on iOS
Fixes #42
2021-04-25 23:16:27 +02:00
Lei Nelissen
fa05935017 Release build 15 2021-04-24 23:18:01 +02:00
Lei Nelissen
2de5cc8e6c Polish search engine UX 2021-04-24 15:30:07 +02:00
Lei Nelissen
24d484ca25 Add track results to search queries 2021-04-24 14:50:43 +02:00
Lei Nelissen
a7b24cf4eb Fix XMLDom issue 2021-04-24 12:21:02 +02:00
Lei Nelissen
eaa1402173 Update packages 2021-04-24 12:18:56 +02:00
Lei Nelissen
1edeb00631 Fix active button color on Android 2021-04-03 15:19:38 +02:00
Lei Nelissen
d422c1ff1e Pull current track from Redux store
Fixes #40
2021-04-03 14:49:49 +02:00
Lei Nelissen
28b330ad4c Update all dependencies 2021-04-03 14:49:01 +02:00
Lei Nelissen
a867513212 Update Fastlane Sentry plugin 2021-03-22 09:49:33 +01:00
Lei Nelissen
14a6341fae Release build 13 2021-03-22 09:24:36 +01:00
Lei Nelissen
645f1307f3 Add OGG Support
Fixes #12
2021-03-21 23:01:07 +01:00
Lei Nelissen
3f16bd6465 Show Album art instead of Track art if an AlbumId is available
Fixes #13
2021-03-21 22:55:35 +01:00
Lei Nelissen
5c15d730b3 Add AirPlay casting button 2021-03-21 22:42:04 +01:00
Lei Nelissen
603bf10154 Switch back to regular react-native-track-player version as patch was merged 2021-03-21 22:12:36 +01:00
Lei Nelissen
57f9e0e08e Upgrade Android build for React Native 0.64 2021-03-21 21:50:35 +01:00
Lei Nelissen
597a348eab Release build 12 2021-03-21 21:50:15 +01:00
Lei Nelissen
8860581450 Upgrade to React Native 0.64 2021-03-21 20:16:26 +01:00
Lei Nelissen
a92ff581ae Merge pull request #38 from yisiliang/master
add Chinese language support
2021-03-21 18:51:04 +01:00
YiSiliang
cd2e333caa add Chinese language support 2021-03-14 21:07:50 +08:00
Lei Nelissen
4ba83b8efc Release build 11 2021-03-09 22:42:34 +01:00
Lei Nelissen
e4daccfe5e Update packages 2021-03-09 22:25:43 +01:00
Lei Nelissen
76c45623a0 Fix launch crash on devices without fallback languages 2021-03-09 22:21:41 +01:00
Lei Nelissen
e4b93ec8c8 Release build 10 2021-02-13 22:16:04 +01:00
Lei Nelissen
4635266273 Add screens for opting into Sentry error tracking 2021-02-13 15:34:43 +01:00
Lei Nelissen
8dc287e56a Make theming somewhat more performant 2021-02-13 12:14:57 +01:00
Lei Nelissen
6cfa8f7624 Attempt fix for JELLYFIN-AUDIO-PLAYER-M 2021-02-11 23:55:49 +01:00
Lei Nelissen
6cf5b8167b Release build 9 2021-02-11 23:47:42 +01:00
Lei Nelissen
dc76ea27d3 Double-check whether an album has tracks 2021-02-11 23:46:03 +01:00
Lei Nelissen
7adc96ba12 Fancy new buttons and more consistent colors 2021-02-11 23:43:21 +01:00
Lei Nelissen
42eb7a169b Release build number 8 2021-02-09 23:33:34 +01:00
Lei Nelissen
3eab7793f5 Allow for HTTP connections in Android Webviews 2021-02-07 23:26:45 +01:00
Lei Nelissen
66eb4bd61d Add environment variables for sentry 2021-02-07 23:19:49 +01:00
Lei Nelissen
3312e99911 Minor package versions upgrade 2021-02-07 23:06:34 +01:00
Lei Nelissen
3ae967e137 Base setup for Sentry 2021-01-29 00:47:17 +01:00
Lei Nelissen
d18daf027e Update packages 2021-01-28 17:40:11 +01:00
Lei Nelissen
0ae7e3ba0c Release new build 2020-12-02 20:51:03 +01:00
Lei Nelissen
e918f089e2 Add Spanish locale as getter 2020-12-02 20:35:46 +01:00
Lei Nelissen
491f019fb3 Merge pull request #30 from raikoon/add-es-locale
Add Spanish locale
2020-12-02 20:24:43 +01:00
unknown
0af730dabe Add Spanish locale 2020-11-25 14:16:26 +01:00
Lei Nelissen
fadfabc868 Merge pull request #29 from leinelissen/translations_src-localisation-lang-en-locale-json--master_fr
Translate '/src/localisation/lang/en/locale.json' in 'fr'
2020-11-11 12:49:54 +01:00
transifex-integration[bot]
de73fe6358 Apply translations in fr
translation completed updated for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.
2020-11-11 11:46:49 +00:00
Lei Nelissen
6f3ed9c988 Release build number 6 2020-11-04 17:05:06 +01:00
Lei Nelissen
ff484cebed Add nl locale as option 2020-11-03 23:10:21 +01:00
Lei Nelissen
1e14ed3271 Merge pull request #23 from leinelissen/translations_src-localisation-lang-en-locale-json--master_nl
Translate '/src/localisation/lang/en/locale.json' in 'nl'
2020-11-03 23:09:40 +01:00
Lei Nelissen
954fe38531 Merge pull request #24 from leinelissen/translations_src-localisation-lang-en-locale-json--master_fr
Translate '/src/localisation/lang/en/locale.json' in 'fr'
2020-11-03 23:09:13 +01:00
transifex-integration[bot]
83cf0e4d56 Apply translations in fr
translation completed for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.
2020-11-03 18:55:06 +00:00
transifex-integration[bot]
3b98eb4bf1 Apply translations in nl
translation completed updated for the source file '/src/localisation/lang/en/locale.json'
on the 'nl' language.
2020-11-03 16:55:52 +00:00
Lei Nelissen
49bb4dfaab Merge branch 'master' of https://github.com/leinelissen/jellyfin-audio-player 2020-11-03 10:21:32 +01:00
Lei Nelissen
9f9e61017e Update project version for release 2020-11-03 10:21:17 +01:00
Lei Nelissen
65c7db9911 Merge pull request #21 from leinelissen/translations_src-localisation-lang-en-locale-json--master_fr
Translate '/src/localisation/lang/en/locale.json' in 'fr' [manual sync]
2020-11-02 23:35:15 +01:00
Lei Nelissen
481a411e4c Delete locale.json 2020-11-02 23:34:19 +01:00
transifex-integration[bot]
8eeddebad7 Apply translations in fr
at least 50% translated updated for the source file '/src/localisation/lang/en/locale.json'
on the 'fr' language.

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

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

3
.editorconfig Normal file
View File

@@ -0,0 +1,3 @@
# Windows files
[*.bat]
end_of_line = crlf

View File

@@ -8,7 +8,7 @@ module.exports = {
'plugin:react/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:react-hooks/recommended',
// "plugin:@typescript-eslint/recommended"
// 'plugin:@typescript-eslint/recommended'
],
globals: {
Atomics: 'readonly',
@@ -28,14 +28,15 @@ module.exports = {
'react-hooks'
],
rules: {
indent: [
indent: 'off',
'@typescript-eslint/indent': [
'error',
4,
{
SwitchCase: 1,
}
],
"linebreak-style": [
'linebreak-style': [
'error',
'unix'
],
@@ -47,10 +48,10 @@ module.exports = {
'error',
'always'
],
"no-unused-vars": "off",
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": [
"error"
'no-unused-vars': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error'
]
}
};

4
.gitattributes vendored
View File

@@ -1 +1,3 @@
*.pbxproj -text
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf

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

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

13
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Lint
on: [push]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Node dependencies
run: npm install
- name: Run linter
run: npm run lint

8
.gitignore vendored
View File

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

12
Gemfile Normal file
View File

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

213
Gemfile.lock Normal file
View File

@@ -0,0 +1,213 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.1)
aws-partitions (1.488.0)
aws-sdk-core (3.119.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.46.0)
aws-sdk-core (~> 3, >= 3.119.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.99.0)
aws-sdk-core (~> 3, >= 3.119.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.4)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.2)
excon (0.85.0)
faraday (1.7.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday_middleware (1.1.0)
faraday (~> 1.0)
fastimage (2.2.5)
fastlane (2.191.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (~> 2.0.0)
naturally (~> 2.2)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-sentry (1.8.1)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.10.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.6.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.5.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.6.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.1.0)
google-cloud-storage (1.34.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (0.17.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (2.0.3)
http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.5.1)
jwt (2.2.3)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.0)
multi_json (1.15.0)
multipart-post (2.0.0)
nanaimo (0.3.0)
naturally (2.2.1)
os (1.1.1)
plist (3.6.0)
public_suffix (4.0.6)
rake (13.0.6)
representable (3.1.1)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.15.0)
addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.1)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
webrick (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.21.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
fastlane (~> 2.153)
fastlane-plugin-sentry
BUNDLED WITH
1.17.2

View File

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

View File

@@ -79,9 +79,11 @@ import com.android.build.OutputFile
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
entryFile: 'index.js'
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/@sentry/react-native/sentry.gradle"
/**
* Set this to true to create two separate APKs instead of one:
@@ -120,20 +122,23 @@ def jscFlavor = 'org.webkit:android-jsc:+'
*/
def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
/**
* Architectures to build native code for in debug.
*/
def nativeArchitectures = project.getProperties().get("reactNativeDebugArchitectures")
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.jellyfinaudioplayer"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
versionCode 5
versionName "1.2.3"
multiDexEnabled true
}
splits {
abi {
@@ -154,6 +159,11 @@ android {
buildTypes {
debug {
signingConfig signingConfigs.debug
if (nativeArchitectures) {
ndk {
abiFilters nativeArchitectures.split(',')
}
}
}
release {
// Caution! In production, you need to generate your own keystore file.
@@ -164,23 +174,17 @@ android {
}
}
packagingOptions {
pickFirst "lib/armeabi-v7a/libc++_shared.so"
pickFirst "lib/arm64-v8a/libc++_shared.so"
pickFirst "lib/x86/libc++_shared.so"
pickFirst "lib/x86_64/libc++_shared.so"
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
@@ -218,7 +222,7 @@ dependencies {
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
from configurations.implementation
into 'libs'
}

View File

@@ -4,5 +4,10 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -9,6 +9,7 @@
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
@@ -21,7 +22,5 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>

View File

@@ -1,6 +1,7 @@
package com.jellyfinaudioplayer;
import com.facebook.react.ReactActivity;
import android.os.Bundle;
public class MainActivity extends ReactActivity {
@@ -12,4 +13,9 @@ public class MainActivity extends ReactActivity {
protected String getMainComponentName() {
return "JellyfinAudioPlayer";
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
}

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

View File

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

View File

@@ -2,17 +2,18 @@
buildscript {
ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 16
compileSdkVersion = 28
targetSdkVersion = 28
buildToolsVersion = "30.0.2"
minSdkVersion = 21
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "21.4.7075529"
}
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:3.5.2")
classpath("com.android.tools.build:gradle:4.2.2")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -21,6 +22,7 @@ buildscript {
allprojects {
repositories {
mavenCentral()
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm

View File

@@ -10,7 +10,7 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
@@ -25,4 +25,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.33.1
FLIPPER_VERSION=0.99.0

View File

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

2
android/gradlew vendored
View File

@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

200
android/gradlew.bat vendored
View File

@@ -1,100 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

4
fastlane/Appfile Normal file
View File

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

87
fastlane/Fastfile Normal file
View File

@@ -0,0 +1,87 @@
default_platform(:ios)
package = load_json(json_path: "package.json")
platform :ios do
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_version_number(
version_number: package["version"],
xcodeproj: "ios/JellyfinAudioPlayer.xcodeproj",
);
increment_build_number(
xcodeproj: "ios/JellyfinAudioPlayer.xcodeproj",
)
build_app(
scheme: "Jellyfin Player",
output_directory: "build",
workspace: "ios/JellyfinAudioPlayer.xcworkspace",
export_method: "app-store",
)
upload_to_testflight
build_number = get_build_number(
xcodeproj: "ios/JellyfinAudioPlayer.xcodeproj",
)
Dir.chdir("..") do
sh(
"npx", "react-native", "bundle",
"--dev", "false",
"--platform", "ios",
"--entry-file", "index.js",
"--bundle-output", "build/index.ios.bundle",
"--sourcemap-output", "build/index.ios.bundle.map"
)
end
sentry_create_release(
version: "1.0+#{build_number}",
app_identifier: 'nl.moeilijkedingen.jellyfinaudioplayer',
finalize: true
)
sentry_upload_dsym(
dsym_path: 'build/Jellyfin Player.app.dSYM.zip',
info_plist: 'ios/JellyfinAudioPlayer/Info.plist',
)
sentry_upload_file(
version: "1.0+#{build_number}",
app_identifier: 'nl.moeilijkedingen.jellyfinaudioplayer',
dist: build_number,
file: 'build/index.ios.bundle',
)
sentry_upload_sourcemap(
version: "1.0+#{build_number}",
app_identifier: 'nl.moeilijkedingen.jellyfinaudioplayer',
dist: build_number,
sourcemap: 'build/index.ios.bundle.map',
rewrite: true
)
end
end
platform :android do
desc "Generate beta build"
lane :beta do
android_set_version_name(
version_name: package['version'],
gradle_file: "android/app/build.gradle"
)
android_set_version_code(
gradle_file: "android/app/build.gradle"
)
gradle(
task: "assembleRelease",
project_dir: "android"
)
end
end

7
fastlane/Pluginfile Normal file
View File

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

38
fastlane/README.md Normal file
View File

@@ -0,0 +1,38 @@
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 beta
```
fastlane ios beta
```
----
## Android
### android beta
```
fastlane android beta
```
Generate beta build
----
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View File

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

8
ios/File.swift Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import <RNFSManager.h>
static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
@@ -36,7 +37,11 @@ static void InitializeFlipper(UIApplication *application) {
moduleName:@"JellyfinAudioPlayer"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];

View File

@@ -17,32 +17,24 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.2.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>34</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>processing</string>
<string>fetch</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>

View File

@@ -15,10 +15,10 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.2.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>34</string>
</dict>
</plist>

View File

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

File diff suppressed because it is too large Load Diff

29996
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +1,79 @@
{
"name": "JellyfinAudioPlayer",
"version": "0.0.1",
"main": "src/index.ts",
"version": "1.2.3",
"main": "src/index.js",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"ios": "react-native run-ios --scheme \"Jellyfin Player\"",
"start": "react-native start",
"test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx && tsc --noEmit",
"build:ios": "react-native bundle --entry-file='index.ts' --bundle-output='./ios/main.jsbundle' --dev=false --platform='ios'"
},
"dependencies": {
"@react-native-community/async-storage": "^1.11.0",
"@react-native-community/masked-view": "^0.1.10",
"@react-native-community/picker": "^1.6.5",
"@react-native-community/slider": "^3.0.0",
"@react-navigation/bottom-tabs": "^5.6.1",
"@react-navigation/native": "^5.6.1",
"@react-navigation/stack": "^5.6.2",
"@reduxjs/toolkit": "^1.4.0",
"@types/lodash": "^4.14.157",
"@types/redux-logger": "^3.0.8",
"@types/styled-components": "^5.1.0",
"date-fns": "^2.14.0",
"fuse.js": "^6.4.0",
"lodash": "^4.17.15",
"react": "16.11.0",
"react-native": "0.62.2",
"react-native-fast-image": "^8.1.5",
"react-native-gesture-handler": "^1.6.1",
"react-native-reanimated": "^1.9.0",
"react-native-safe-area-context": "^3.0.7",
"react-native-screens": "^2.9.0",
"react-native-svg": "^12.1.0",
"react-native-svg-transformer": "^0.14.3",
"react-native-track-player": "github:leinelissen/react-native-track-player",
"react-native-webview": "^10.3.2",
"react-redux": "^7.2.0",
"redux": "^4.0.5",
"@react-native-community/async-storage": "^1.12.1",
"@react-native-community/masked-view": "^0.1.11",
"@react-native-community/netinfo": "^7.1.7",
"@react-native-community/picker": "^1.8.1",
"@react-native-community/slider": "^4.1.12",
"@react-navigation/bottom-tabs": "^6.0.9",
"@react-navigation/native": "^6.0.6",
"@react-navigation/stack": "^6.0.11",
"@reduxjs/toolkit": "^1.7.1",
"@sentry/react-native": "^3.2.10",
"@types/lodash": "^4.14.178",
"date-fns": "^2.28.0",
"events": "^3.3.0",
"fuse.js": "^6.5.3",
"i18n-js": "^3.8.0",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-native": "^0.66.4",
"react-native-airplay-button": "^1.1.0",
"react-native-collapsible": "^1.6.0",
"react-native-dotenv": "^3.3.1",
"react-native-fast-image": "^8.5.11",
"react-native-flipper": "^0.127.0",
"react-native-fs": "^2.18.0",
"react-native-gesture-handler": "^2.1.0",
"react-native-localize": "^2.1.7",
"react-native-safe-area-context": "^3.3.2",
"react-native-screens": "^3.10.1",
"react-native-svg": "^12.2.0",
"react-native-svg-transformer": "^1.0.0",
"react-native-track-player": "^2.1.2",
"react-native-webview": "^11.15.0",
"react-redux": "^7.2.6",
"redux": "^4.1.2",
"redux-flipper": "^2.0.1",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"styled-components": "^5.1.1"
"styled-components": "^5.3.3"
},
"devDependencies": {
"@babel/core": "^7.10.4",
"@babel/runtime": "^7.10.4",
"@react-native-community/eslint-config": "^1.0.0",
"@types/jest": "^24.0.24",
"@types/react-native": "^0.62.17",
"@types/react-redux": "^7.1.9",
"@types/react-test-renderer": "16.9.2",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"babel-jest": "^24.9.0",
"babel-plugin-module-resolver": "^4.0.0",
"eslint": "^6.5.1",
"eslint-plugin-react-hooks": "^4.0.5",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.58.0",
"react-test-renderer": "16.11.0",
"typescript": "^3.9.6"
"@babel/core": "^7.16.7",
"@babel/runtime": "^7.16.7",
"@react-native-community/eslint-config": "^3.0.1",
"@sentry/cli": "^1.71.0",
"@types/i18n-js": "^3.8.2",
"@types/jest": "^27.4.0",
"@types/react-native": "^0.66.10",
"@types/react-redux": "^7.1.21",
"@types/react-test-renderer": "^17.0.1",
"@types/redux-logger": "^3.0.9",
"@types/styled-components": "^5.1.19",
"@types/styled-components-react-native": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"babel-jest": "^27.4.5",
"babel-plugin-module-resolver": "^4.1.0",
"eslint": "^8.5.0",
"eslint-plugin-react-hooks": "^4.3.0",
"jest": "^27.4.5",
"metro-react-native-babel-preset": "^0.66.2",
"react-test-renderer": "^17.0.2",
"typescript": "^4.5.4"
},
"jest": {
"preset": "react-native",
@@ -72,5 +85,8 @@
"json",
"node"
]
},
"overrides": {
"@types/react-native": "^0.66.10"
}
}

View File

@@ -1,3 +1,4 @@
export const ALBUM_CACHE_AMOUNT_OF_DAYS = 7;
export const PLAYLIST_CACHE_AMOUNT_OF_DAYS = 7;
export const ALPHABET_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ';
export const THEME_COLOR = '#FF3C00';

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.74512 9.09863C8 9.09863 8.20215 9.01953 8.36035 8.85254L11.9639 5.22266C12.1572 5.0293 12.2451 4.81836 12.2451 4.57227C12.2451 4.33496 12.1484 4.10645 11.9639 3.93066L8.36035 0.265625C8.20215 0.0898438 8 0.00195312 7.74512 0.00195312C7.27051 0.00195312 6.89258 0.397461 6.89258 0.880859C6.89258 1.11816 6.98047 1.31152 7.12988 1.47852L9.23047 3.53516C8.81738 3.47363 8.39551 3.43848 7.97363 3.43848C3.62305 3.43848 0.142578 6.91895 0.142578 11.2783C0.142578 15.6377 3.64941 19.1445 8 19.1445C12.3594 19.1445 15.8574 15.6377 15.8574 11.2783C15.8574 10.751 15.4883 10.373 14.9609 10.373C14.4512 10.373 14.1084 10.751 14.1084 11.2783C14.1084 14.6709 11.3926 17.3955 8 17.3955C4.61621 17.3955 1.8916 14.6709 1.8916 11.2783C1.8916 7.85938 4.58984 5.15234 7.97363 5.15234C8.54492 5.15234 9.07227 5.19629 9.53809 5.27539L7.13867 7.64844C6.98047 7.80664 6.89258 8 6.89258 8.2373C6.89258 8.7207 7.27051 9.09863 7.74512 9.09863Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 18.7334C14.9658 18.7334 19.0791 14.6289 19.0791 9.6543C19.0791 4.68848 14.9658 0.575195 9.99121 0.575195C5.02539 0.575195 0.920898 4.68848 0.920898 9.6543C0.920898 14.6289 5.03418 18.7334 10 18.7334ZM10 16.9492C5.95703 16.9492 2.71387 13.6973 2.71387 9.6543C2.71387 5.61133 5.94824 2.36816 9.99121 2.36816C14.0342 2.36816 17.2861 5.61133 17.2949 9.6543C17.2949 13.6973 14.043 16.9492 10 16.9492ZM9.98242 12.335C10.1758 12.335 10.3428 12.2559 10.4834 12.124L13.3838 9.20605C13.542 9.05664 13.5947 8.88965 13.5947 8.71387C13.5947 8.35352 13.3398 8.07227 12.9707 8.07227C12.7861 8.07227 12.6191 8.14258 12.4873 8.27441L11.9336 8.82812L10.5449 10.4014L10.6416 8.90723V5.24219C10.6416 4.85547 10.3691 4.57422 9.98242 4.57422C9.58691 4.57422 9.32324 4.85547 9.32324 5.24219V8.90723L9.42871 10.4102L8.01367 8.81934L7.50391 8.27441C7.38086 8.13379 7.22266 8.07227 7.02051 8.07227C6.65137 8.07227 6.3877 8.33594 6.3877 8.72266C6.3877 8.87207 6.4668 9.07422 6.58984 9.19727L9.49023 12.124C9.63086 12.2646 9.79785 12.335 9.98242 12.335ZM6.7832 14.2598H13.208C13.5859 14.2598 13.8584 13.9785 13.8584 13.6006C13.8584 13.2314 13.5859 12.959 13.208 12.959H6.7832C6.40527 12.959 6.13281 13.2314 6.13281 13.6006C6.13281 13.9785 6.40527 14.2598 6.7832 14.2598Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="21" viewBox="0 0 24 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.63672 15.2002H10.9453V9.82129C10.9453 9.25 11.4287 8.78418 12 8.78418C12.5713 8.78418 13.0459 9.25 13.0459 9.82129V15.2002H18.8643C21.4658 15.2002 23.2324 13.6006 23.2324 11.4033C23.2324 9.58398 22.1689 7.98438 20.4199 7.27246C20.4287 3.2998 17.5635 0.43457 13.8809 0.43457C11.543 0.43457 9.78516 1.63867 8.68652 3.22949C6.58594 2.71094 4.10742 4.30176 4.01953 6.78027C2.00684 7.13184 0.767578 8.90723 0.767578 10.9902C0.767578 13.3193 2.70996 15.2002 5.63672 15.2002ZM12 20.5791C12.2109 20.5791 12.4131 20.5088 12.624 20.2979L15.5771 17.4502C15.7266 17.3008 15.8057 17.1426 15.8057 16.9316C15.8057 16.5186 15.4717 16.2197 15.0674 16.2197C14.8652 16.2197 14.6631 16.3076 14.5225 16.4658L13.3008 17.7666L12.7295 18.417L12.8174 17.1162V15.0859C12.8174 14.6465 12.4482 14.2773 12 14.2773C11.543 14.2773 11.1738 14.6465 11.1738 15.0859V17.1162L11.2617 18.417L10.6992 17.7666L9.46875 16.4658C9.32812 16.3076 9.11719 16.2197 8.91504 16.2197C8.51074 16.2197 8.18555 16.5186 8.18555 16.9316C8.18555 17.1426 8.27344 17.3008 8.42285 17.4502L11.376 20.2979C11.5869 20.5088 11.7803 20.5791 12 20.5791Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.3281 15.6592C21.0703 15.6592 23.2324 13.6465 23.2324 11.1328C23.2324 9.26074 22.1689 7.59961 20.4199 6.87012C20.4287 2.89746 17.5635 0.0322266 13.8809 0.0322266C11.543 0.0322266 9.78516 1.23633 8.68652 2.83594C6.4541 2.23828 4.10742 3.89941 4.01953 6.37793C2.00684 6.73828 0.767578 8.54004 0.767578 10.7461C0.767578 13.418 3.10547 15.6504 6.18164 15.6504L18.3281 15.6592ZM18.3281 13.9014H6.19043C4.09863 13.9014 2.54297 12.4424 2.54297 10.7461C2.54297 8.98828 3.62402 7.6875 5.41699 7.6875C5.54883 7.6875 5.60156 7.61719 5.59277 7.49414C5.54004 4.88379 7.41211 3.9873 9.30176 4.58496C9.41602 4.62012 9.48633 4.59375 9.53906 4.49707C10.4092 2.96777 11.6924 1.78125 13.8721 1.78125C16.6318 1.78125 18.6006 3.96973 18.7324 6.52734C18.7588 7.00195 18.7236 7.51172 18.6885 7.93359C18.6709 8.05664 18.7236 8.12695 18.8379 8.14453C20.4287 8.45215 21.457 9.57715 21.457 11.1328C21.457 12.6709 20.0947 13.9014 18.3281 13.9014ZM12.0088 9.4541C12.4658 9.4541 12.7207 9.19922 12.7471 8.70703L12.8701 5.75391C12.8965 5.24414 12.5098 4.88379 12 4.88379C11.4814 4.88379 11.1123 5.23535 11.1387 5.75391L11.2529 8.71582C11.2793 9.19043 11.5342 9.4541 12.0088 9.4541ZM12 12.2402C12.5537 12.2402 12.9932 11.8447 12.9932 11.3174C12.9932 10.7725 12.5625 10.3857 12 10.3857C11.4375 10.3857 11.0068 10.7812 11.0068 11.3174C11.0068 11.8447 11.4463 12.2402 12 12.2402Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5674 20.4658C23.7207 19.9736 25.2324 18.2334 25.2324 16.1328C25.2324 14.2607 24.1689 12.5996 22.4199 11.8701C22.4287 7.89746 19.5635 5.03223 15.8809 5.03223C13.543 5.03223 11.7852 6.23633 10.6865 7.83594C10.124 7.67773 9.50879 7.67773 8.9375 7.83594L21.5674 20.4658ZM20.3369 22.7773C20.6182 23.0498 21.0576 23.0498 21.3301 22.7773C21.5938 22.5049 21.6025 22.0566 21.3301 21.7842L6.52051 6.9834C6.23926 6.70215 5.78223 6.71973 5.51855 6.9834C5.25488 7.24707 5.26367 7.71289 5.51855 7.96777L20.3369 22.7773ZM8.18164 20.6592H16.6631L6.24805 10.2705C6.11621 10.6045 6.03711 10.9736 6.01953 11.3779C4.00684 11.7383 2.76758 13.54 2.76758 15.7461C2.76758 18.418 5.10547 20.6592 8.18164 20.6592Z" />
</svg>

After

Width:  |  Height:  |  Size: 808 B

3
src/assets/cloud.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.3281 15.6504C21.0703 15.6504 23.2324 13.6377 23.2324 11.124C23.2324 9.26074 22.1689 7.59961 20.4199 6.87012C20.4287 2.88867 17.5635 0.0234375 13.8809 0.0234375C11.543 0.0234375 9.78516 1.23633 8.68652 2.82715C6.4541 2.22949 4.10742 3.89062 4.01953 6.36914C2.00684 6.72949 0.767578 8.54004 0.767578 10.7373C0.767578 13.418 3.10547 15.6504 6.18164 15.6504H18.3281Z" />
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,3 @@
<svg width="22" height="16" viewBox="0 0 22 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.33789 6.74023H17.6621C18.5498 6.74023 19.3496 6.94238 20.0439 7.30273L17.873 2.21387C17.293 0.851562 16.1152 0.0957031 14.542 0.0957031H7.45801C5.87598 0.0957031 4.70703 0.851562 4.12695 2.21387L1.94727 7.29395C2.6416 6.94238 3.4502 6.74023 4.33789 6.74023ZM4.33789 15.2305H17.6621C19.8066 15.2305 21.2832 13.7627 21.2832 11.6357C21.2832 9.5 19.8066 8.03223 17.6621 8.03223H4.33789C2.18457 8.03223 0.708008 9.5 0.708008 11.6357C0.708008 13.7627 2.18457 15.2305 4.33789 15.2305ZM16.4316 11.6357C16.4316 11.0029 16.9678 10.4668 17.6094 10.4668C18.2422 10.4668 18.7783 11.0029 18.7783 11.6357C18.7783 12.2773 18.2422 12.7959 17.6094 12.7959C16.9678 12.8047 16.4316 12.2861 16.4316 11.6357Z" />
</svg>

After

Width:  |  Height:  |  Size: 806 B

View 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

View 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
View 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
View File

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

After

Width:  |  Height:  |  Size: 695 B

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

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

After

Width:  |  Height:  |  Size: 964 B

3
src/assets/trash.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.84277 22.4785H18.166C19.3701 22.4785 20.0732 21.8369 20.126 20.6416L20.6797 7.94141H21.8926C22.3408 7.94141 22.6836 7.58984 22.6836 7.15039C22.6836 6.71094 22.332 6.37695 21.8926 6.37695H18.0781V5.05859C18.0781 3.65234 17.1729 2.81738 15.6611 2.81738H12.3213C10.8096 2.81738 9.9043 3.65234 9.9043 5.05859V6.37695H6.10742C5.66797 6.37695 5.31641 6.71973 5.31641 7.15039C5.31641 7.59863 5.66797 7.94141 6.10742 7.94141H7.3291L7.8916 20.6416C7.93555 21.8369 8.63867 22.4785 9.84277 22.4785ZM11.7324 5.1377C11.7324 4.74219 12.0049 4.4873 12.4443 4.4873H15.5469C15.9863 4.4873 16.2588 4.74219 16.2588 5.1377V6.37695H11.7324V5.1377ZM11.1787 19.7803C10.8271 19.7803 10.5811 19.5518 10.5723 19.2002L10.3086 9.86621C10.2998 9.51465 10.5459 9.27734 10.915 9.27734C11.2666 9.27734 11.5127 9.50586 11.5215 9.85742L11.7852 19.1914C11.8027 19.543 11.5566 19.7803 11.1787 19.7803ZM14 19.7803C13.6309 19.7803 13.3848 19.5518 13.3848 19.2002V9.85742C13.3848 9.51465 13.6309 9.27734 14 9.27734C14.3691 9.27734 14.624 9.51465 14.624 9.85742V19.2002C14.624 19.5518 14.3691 19.7803 14 19.7803ZM16.8213 19.7891C16.4434 19.7891 16.1973 19.543 16.2148 19.2002L16.4785 9.85742C16.4873 9.50586 16.7334 9.27734 17.085 9.27734C17.4541 9.27734 17.7002 9.51465 17.6914 9.86621L17.4277 19.2002C17.4189 19.5518 17.1729 19.7891 16.8213 19.7891Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -9,10 +9,10 @@ import {
TapGestureHandlerGestureEvent
} from 'react-native-gesture-handler';
interface LetterContainerProps {
onPress: (letter: string) => void;
letter: string;
}
// interface LetterContainerProps {
// onPress: (letter: string) => void;
// letter: string;
// }
const Container = styled.View`
position: absolute;

View File

@@ -1,50 +1,51 @@
import React, { Component } from 'react';
import React, { useEffect } from 'react';
import { Provider } from 'react-redux';
import TrackPlayer from 'react-native-track-player';
import TrackPlayer, { Capability } from 'react-native-track-player';
import { PersistGate } from 'redux-persist/integration/react';
import { NavigationContainer } from '@react-navigation/native';
import Routes from '../screens';
import store, { persistedStore } from 'store';
import {
NavigationContainer,
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
import { useColorScheme } from 'react-native';
import { ColorSchemeContext, themes } from './Colors';
import DownloadManager from './DownloadManager';
// import ErrorReportingAlert from 'utility/ErrorReportingAlert';
interface State {
isReady: boolean;
}
export default function App(): JSX.Element {
const colorScheme = useColorScheme();
// const colorScheme = 'dark';
const theme = themes[colorScheme || 'light'];
export default class App extends Component<State> {
state = {
isReady: false
};
async componentDidMount() {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
capabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
TrackPlayer.CAPABILITY_STOP,
TrackPlayer.CAPABILITY_SEEK_TO,
]
});
this.setState({ isReady: true });
}
render() {
const { isReady } = this.state;
if (!isReady) {
return null;
useEffect(() => {
async function setupTrackPlayer() {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
capabilities: [
Capability.Play,
Capability.Pause,
Capability.SkipToNext,
Capability.SkipToPrevious,
Capability.Stop,
Capability.SeekTo,
]
});
}
setupTrackPlayer();
}, []);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistedStore}>
<NavigationContainer>
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistedStore}>
<ColorSchemeContext.Provider value={theme}>
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Routes />
<DownloadManager />
</NavigationContainer>
</PersistGate>
</Provider>
);
}
</ColorSchemeContext.Provider>
</PersistGate>
</Provider>
);
}

73
src/components/Button.tsx Normal file
View File

@@ -0,0 +1,73 @@
import React, { useCallback, useState } from 'react';
import { SvgProps } from 'react-native-svg';
import {
PressableProps, ViewProps, View,
} 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'];
}
const BaseButton = styled.Pressable`
padding: 16px;
border-radius: 8px;
flex-direction: row;
align-items: center;
justify-content: center;
flex-grow: 1;
${(props) => props.disabled && css`
opacity: 0.25;
`}
`;
const ButtonText = styled.Text<{ active?: boolean }>`
color: ${THEME_COLOR};
font-weight: 600;
${props => props.active && css`
color: white;
`}
`;
const Button = React.forwardRef<View, ButtonProps>(function Button(props, ref) {
const { icon: Icon, title, disabled, ...rest } = props;
const defaultStyles = useDefaultStyles();
const [isPressed, setPressed] = useState(false);
const handlePressIn = useCallback(() => setPressed(true), []);
const handlePressOut = useCallback(() => setPressed(false), []);
return (
<BaseButton
{...rest}
disabled={disabled}
// @ts-expect-error styled-components has outdated react-native typings
ref={ref}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
style={[
props.style,
{ backgroundColor: isPressed ? THEME_COLOR : defaultStyles.button.backgroundColor }
]}
>
{Icon &&
<Icon
width={14}
height={14}
fill={isPressed ? '#fff' : THEME_COLOR}
style={{
marginRight: 8,
}}
/>
}
<ButtonText active={isPressed}>{title}</ButtonText>
</BaseButton>
);
});
export default Button;

80
src/components/Colors.ts Normal file
View 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' ? '26' : '16'}`,
},
imageBackground: {
backgroundColor: scheme === 'dark' ? '#333' : '#ddd',
},
modal: {
backgroundColor: scheme === 'dark' ? '#22222200' : '#eeeeee00',
},
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);
}

View File

@@ -0,0 +1,115 @@
import React, { useEffect, useMemo, useRef } from 'react';
import { useTypedSelector } from 'store';
import CloudIcon from 'assets/cloud.svg';
import CloudDownArrow from 'assets/cloud-down-arrow.svg';
import CloudExclamationMarkIcon from 'assets/cloud-exclamation-mark.svg';
import InternalDriveIcon from 'assets/internal-drive.svg';
import useDefaultStyles from './Colors';
import { EntityId } from '@reduxjs/toolkit';
import Svg, { Circle, CircleProps } from 'react-native-svg';
import { Animated, Easing } from 'react-native';
import styled from 'styled-components/native';
interface DownloadIconProps {
trackId: EntityId;
size?: number;
fill?: string;
}
const DownloadContainer = styled.View`
position: relative;
`;
const IconOverlay = styled.View`
position: absolute;
top: 0;
left: 0;
transform: scale(0.5);
`;
function DownloadIcon({ trackId, size = 16, fill }: DownloadIconProps) {
// determine styles
const defaultStyles = useDefaultStyles();
const iconFill = fill || defaultStyles.textHalfOpacity.color;
// Get download icon from state
const entity = useTypedSelector((state) => state.downloads.entities[trackId]);
const isQueued = useTypedSelector((state) => state.downloads.queued.includes(trackId));
// Memoize calculations for radius and circumference of the circle
const radius = useMemo(() => size / 2, [size]);
const circumference = useMemo(() => radius * 2 * Math.PI, [radius]);
// Initialize refs for the circle and the animated value
const circleRef = useRef<Circle>(null);
const offsetAnimation = useRef(new Animated.Value(entity?.progress || 0)).current;
// Whenever the progress changes, trigger the animation
useEffect(() => {
Animated.timing(offsetAnimation, {
toValue: (circumference * (1 - (entity?.progress || 0))),
duration: 250,
useNativeDriver: false,
easing: Easing.ease,
}).start();
}, [entity?.progress, offsetAnimation, circumference]);
// On mount, subscribe to changes in the animation value and then
// apply them to the circle using native props
useEffect(() => {
const subscription = offsetAnimation.addListener((offset) => {
// @ts-expect-error undocumented functionality
const setNativeProps = circleRef.current?.setNativeProps as (props: CircleProps) => void | undefined;
setNativeProps?.({ strokeDashoffset: offset.value });
});
return () => offsetAnimation.removeListener(subscription);
}, [offsetAnimation]);
if (!entity && !isQueued) {
return (
<CloudIcon width={size} height={size} fill={iconFill} />
);
}
if (entity?.isComplete) {
return (
<InternalDriveIcon width={size} height={size} fill={iconFill} />
);
}
if (entity?.isFailed) {
return (
<CloudExclamationMarkIcon width={size} height={size} fill={iconFill} />
);
}
if (isQueued || (!entity?.isFailed && !entity?.isComplete)) {
return (
<DownloadContainer>
<Svg width={size} height={size} transform={[{ rotate: '-90deg' }]}>
<Circle
cx={radius}
cy={radius}
r={radius - 1}
stroke={iconFill}
// @ts-expect-error react-native-svg has outdated react-native typings
ref={circleRef}
strokeWidth={1.5}
strokeDasharray={[ circumference, circumference ]}
strokeDashoffset={circumference}
strokeLinecap='round'
fill='transparent'
/>
</Svg>
<IconOverlay>
<CloudDownArrow width={size} height={size} fill={iconFill} />
</IconOverlay>
</DownloadContainer>
);
}
return null;
}
export default DownloadIcon;

View File

@@ -0,0 +1,110 @@
import { EntityId } from '@reduxjs/toolkit';
import { xor } from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { DocumentDirectoryPath, readDir } from 'react-native-fs';
import { useDispatch } from 'react-redux';
import { useTypedSelector } from 'store';
import { completeDownload, downloadTrack } from 'store/downloads/actions';
/**
* The maximum number of concurrent downloads we allow to take place at once.
* This is hardcoded at 5 for now, but might be extracted to a setting later.
*/
const MAX_CONCURRENT_DOWNLOADS = 5;
/**
* This is a component that tracks queued downloads, and starts them one-by-one,
* so that we don't overload react-native-fs, as well as the render performance.
*/
function DownloadManager () {
// Retrieve store helpers
const { queued, ids } = useTypedSelector((state) => state.downloads);
const rehydrated = useTypedSelector((state) => state._persist.rehydrated);
const dispatch = useDispatch();
// Keep state for the currently active downloads (i.e. the downloads that
// have actually been pushed out to react-native-fs).
const [hasRehydratedOrphans, setHasRehydratedOrphans] = useState(false);
const activeDownloads = useRef(new Set<EntityId>());
useEffect(() => {
// GUARD: Check if the queue is empty
if (!queued.length) {
// If so, clear any current downloads
activeDownloads.current.clear();
return;
}
// Apparently, the queue has changed, and we need to manage
// First, we pick the first n downloads
const queue = queued.slice(0, MAX_CONCURRENT_DOWNLOADS);
// We then filter for new downloads
queue.filter((id) => !activeDownloads.current.has(id))
.forEach((id) => {
// We dispatch the actual call to start downloading
dispatch(downloadTrack(id));
// And add it to the active downloads
activeDownloads.current.add(id);
});
// Lastly, if something isn't part of the queue, but is of active
// downloads, we can assume the download completed.
xor(Array.from(activeDownloads.current), queue)
.forEach((id) => activeDownloads.current.delete(id));
}, [queued, dispatch, activeDownloads]);
useEffect(() => {
// GUARD: We only run this functino once
if (hasRehydratedOrphans) {
return;
}
// GUARD: If the state has not been rehydrated, we cannot check against
// the store ids.
if (!rehydrated) {
return;
}
console.log(ids);
/**
* Whenever the store is cleared, existing downloads get "lost" because
* the only reference we have is the store. This function checks for
* those lost downloads and adds them to the store
*/
async function hydrateOrphanedDownloads() {
// Retrieve all files for this app
const files = await readDir(DocumentDirectoryPath);
// Loop through the mp3 files
files.filter((file) => file.isFile() && file.name.endsWith('.mp3'))
.forEach((file) => {
const id = file.name.replace('.mp3', '');
console.log(id, ids.includes(id));
// GUARD: If the id is already in the store, there's nothing
// left for us to do.
if (ids.includes(id)) {
return;
}
// Add the download to the store
dispatch(completeDownload({
id,
location: file.path,
size: Number.parseInt(file.size),
}));
});
}
hydrateOrphanedDownloads();
setHasRehydratedOrphans(true);
}, [rehydrated, ids, hasRehydratedOrphans, dispatch]);
return null;
}
export default DownloadManager;

View File

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

View File

@@ -1,24 +1,49 @@
import React from 'react';
import { TouchableOpacityProps, Text } from 'react-native';
import React, { useCallback, useState } from 'react';
import { TouchableOpacityProps } from 'react-native';
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 useDefaultStyles from './Colors';
const BUTTON_SIZE = 14;
const Container = styled.TouchableOpacity`
padding: 18px 0;
const Container = styled.Pressable<{ active?: boolean }>`
padding: 18px 20px;
border-bottom-width: 1px;
border-bottom-color: #eee;
flex-direction: row;
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 defaultStyles = useDefaultStyles();
const [isPressed, setPressed] = useState(false);
const handlePressIn = useCallback(() => setPressed(true), []);
const handlePressOut = useCallback(() => setPressed(false), []);
return (
<Container {...props}>
<Text style={{ color: THEME_COLOR, fontSize: 16 }}>{children}</Text>
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={THEME_COLOR} />
// @ts-expect-error styled-components has outdated react-native typings
<Container
{...props}
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>
);
};

View File

@@ -1,27 +1,44 @@
import React from 'react';
import styled from 'styled-components/native';
import { SafeAreaView } from 'react-native';
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components/native';
import { Pressable } from 'react-native';
import { useNavigation, StackActions } from '@react-navigation/native';
import useDefaultStyles from './Colors';
const Background = styled.View`
background-color: #eeeeeeee;
padding: 100px 25px;
interface Props {
fullSize?: boolean;
}
const Background = styled(Pressable)`
flex: 1;
justify-content: center;
`;
const Container = styled.View`
background-color: white;
border-radius: 20px;
flex: 1;
const Container = styled(Pressable)<Pick<Props, 'fullSize'>>`
margin: auto 20px;
padding: 4px;
border-radius: 12px;
flex: 0 0 auto;
background: salmon;
${props => props.fullSize && css`
flex: 1;
margin: auto 0;
border-radius: 0px;
`}
`;
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 (
<Background>
<SafeAreaView style={{ flex: 1}}>
<Container>
{children}
</Container>
</SafeAreaView>
<Background style={defaultStyles.modal} onPress={closeModal}>
<Container style={defaultStyles.modalInner} fullSize={fullSize}>
{children}
</Container>
</Background>
);
};

11
src/components/Text.tsx Normal file
View 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]} />
);
}

View File

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

View File

@@ -1,7 +1,14 @@
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;
font-size: 36px;
font-weight: bold;
`;
export const SubHeader = styled(Text)`
font-size: 24px;
margin: 12px 0;
font-weight: 500;
`;

View File

@@ -0,0 +1,13 @@
import styled from 'styled-components/native';
import Button from './Button';
export const WrappableButtonRow = styled.View`
flex: 0 0 auto;
flex-direction: row;
flex-wrap: wrap;
margin: 6px -2px;
`;
export const WrappableButton = styled(Button)`
margin: 2px;
`;

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

@@ -0,0 +1,52 @@
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'),
zh: () => require('./lang/zh/locale.json'),
ja: () => require('./lang/ja/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;

View File

@@ -0,0 +1,60 @@
{
"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",
"track": "Track",
"playlists": "Playlists",
"playlist": "Playlist",
"play-playlist": "Play Playlist",
"shuffle-album": "Shuffle Album",
"shuffle-playlist": "Shuffle Playlist",
"downloads": "Downloads",
"download-track": "Download Track",
"download-album": "Download Album",
"download-playlist": "Download Playlist",
"no-downloads": "You have not yet downloaded any tracks",
"delete-track": "Delete Track",
"delete-all-tracks": "Delete All Tracks",
"delete-album": "Delete Album",
"delete-playlist": "Delete Playlist",
"total-download-size": "Total Download Size",
"retry-failed-downloads": "Retry Failed Downloads",
"you-are-offline-message": "You are currently offline. You can only play previously downloaded music."
}

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
{
"play-next": "再生する",
"play-album": "アルバムを再生する",
"queue": "再生リスト",
"add-to-queue": "再生リストに追加",
"clear-queue": "再生リストをクリア",
"no-results": "結果なし",
"album": "アルバム",
"albums": "アルバム",
"all-albums": "すべてのアルバム",
"search": "検索",
"music": "音楽",
"now-playing": "再生中",
"onboarding-welcome": "ようこそ。",
"onboarding-intro": "Jellyfin Audio Player は、どこからでも音楽ライブラリをストリーミングすることができます。バックグラウンド・オーディオやキャスティングを完全にサポートします。",
"onboarding-cta": "始めるためには、Jellyfin サーバーが必要です。下のボタンをクリックして、Jellyfin サーバーのアドレスを入力し、ログインしてください。",
"set-jellyfin-server": "Jellyfin サーバ を設定",
"set-jellyfin-server-instruction": "Jellyfin サーバの URL を入力してください。プロトコルとポートを必ず含めてください。",
"settings": "設定",
"jellyfin-library": "Jellyfin ライブライ",
"jellyfin-server-url": "Jellyfin サーバの URL",
"jellyfin-access-token": "Jellyfin アクセストークン",
"jellyfin-user-id": "Jellyfin ユーザ ID",
"setting-cache": "キャッシュ",
"setting-cache-description": "Jellyfinライブラリをアップデートしたにもかかわらず、アプリがキャッシュされたアセットを保持している場合、このボタンを使って強制的にキャッシュをクリアすることができます。これにより、アプリはライブラリを最初から取得するようになります。",
"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": "エラー、デバイスタイプ、OSバージョン、アプリバージョン、デバイスIDが記録されます。いかなるエラー報告においても、アプリケーションの状態は送信されません。デバイスIDは、デバイスの設定でリセット可能な一意のハッシュであり、この識別子から個人情報を推測することはできません。",
"where-is-data-stored": "データはどこに保存されていますか?",
"where-is-data-stored-description": "Sentryサービスは、私たち自身のインフラでセルフホストされています。当社以外の誰も、サーバー、データベース、アプリケーション、データログにアクセスすることはできません。このインフラはEUでホストされています。",
"enable-error-reporting": "エラー報告を有効にしますか?",
"enable-error-reporting-description": "これは、クラッシュやエラーのレポートを送信することで、アプリを改善に役立ちます。",
"enable": "有効する",
"disable": "無効する",
"more-info": "詳しくは",
"track": "追跡"
}

View File

@@ -0,0 +1,61 @@
{
"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 achtergroundaudio 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": "Stel 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",
"track": "Track",
"playlists": "Playlists",
"playlist": "Playlist",
"play-playlist": "Speel Playlist",
"shuffle-album": "Shuffle Album",
"shuffle-playlist": "Shuffle Playlist",
"downloads": "Downloads",
"download-track": "Download Track",
"download-album": "Download Album",
"download-playlist": "Download Playlist",
"no-downloads": "Je hebt nog geen nummers gedownload",
"delete-track": "Verwijder Track",
"delete-all-tracks": "Verwijder alle nummers",
"delete-album": "Verwijder Album",
"delete-playlist": "Verwijder Playlist",
"total-download-size": "Totale grootte downloads",
"retry-failed-downloads": "Probeer Mislukte Downloads Opnieuw",
"you-are-offline-message": "Je bent op dit moment offline. Je kunt alleen eerder gedownloade nummers afspelen."
}

View File

@@ -0,0 +1,42 @@
{
"play-next": "播放下一首",
"play-album": "播放专辑",
"queue": "歌曲队列",
"add-to-queue": "加入队列",
"clear-queue": "清除队列",
"no-results": "无匹配记录",
"album": "专辑",
"albums": "专辑",
"all-albums": "所有专辑",
"search": "搜索",
"music": "歌曲",
"now-playing": "正在播放",
"onboarding-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": "设置Jellyfin服务器",
"set-jellyfin-server-instruction": "设置Jellyfin服务器的完整网址包括HTTP/HTTS和端口。",
"settings": "设置",
"jellyfin-library": "Jellyfin库",
"jellyfin-server-url": "Jellyfin服务器网址",
"jellyfin-access-token": "Jellyfin Access Token",
"jellyfin-user-id": "Jellyfin用户ID",
"setting-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": "清除缓存",
"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": "启用",
"disable": "禁用",
"more-info": "更多信息"
}

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

@@ -0,0 +1,57 @@
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'
| 'track'
| 'playlists'
| 'playlist'
| 'play-playlist'
| 'shuffle-album'
| 'shuffle-playlist'
| 'downloads'
| 'download-track'
| 'download-album'
| 'download-playlist'
| 'delete-album'
| 'delete-playlist'
| 'delete-track'
| 'total-download-size'
| 'no-downloads'
| 'retry-failed-downloads'
| 'you-are-offline-message'

View File

@@ -0,0 +1,144 @@
import useDefaultStyles from 'components/Colors';
import React, { useCallback, useMemo } from 'react';
import { FlatListProps, Text, TouchableOpacity, View } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useTypedSelector } from 'store';
import formatBytes from 'utility/formatBytes';
import TrashIcon from 'assets/trash.svg';
import ArrowClockwise from 'assets/arrow-clockwise.svg';
import { THEME_COLOR } from 'CONSTANTS';
import { useDispatch } from 'react-redux';
import { EntityId } from '@reduxjs/toolkit';
import { queueTrackForDownload, removeDownloadedTrack } from 'store/downloads/actions';
import Button from 'components/Button';
import { t } from 'i18n-js';
import DownloadIcon from 'components/DownloadIcon';
import styled from 'styled-components/native';
const DownloadedTrack = styled.View`
flex: 1 0 auto;
flex-direction: row;
padding: 8px 0;
align-items: center;
margin: 0 20px;
border-bottom-width: 1px;
`;
function Downloads() {
const defaultStyles = useDefaultStyles();
const dispatch = useDispatch();
const { entities, ids } = useTypedSelector((state) => state.downloads);
const tracks = useTypedSelector((state) => state.music.tracks.entities);
// Calculate the total download size
const totalDownloadSize = useMemo(() => (
ids?.reduce<number>((sum, id) => sum + (entities[id]?.size || 0), 0)
), [ids, entities]);
/**
* Handlers for actions in this components
*/
// Delete a single downloaded track
const handleDelete = useCallback((id: EntityId) => {
dispatch(removeDownloadedTrack(id));
}, [dispatch]);
// Delete all downloaded tracks
const handleDeleteAllTracks = useCallback(() => ids.forEach(handleDelete), [handleDelete, ids]);
// Retry a single failed track
const retryTrack = useCallback((id: EntityId) => {
dispatch(queueTrackForDownload(id));
}, [dispatch]);
// Retry all failed tracks
const failedIds = useMemo(() => ids.filter((id) => !entities[id]?.isComplete), [ids, entities]);
const handleRetryFailed = useCallback(() => (
failedIds.forEach(retryTrack)
), [failedIds, retryTrack]);
/**
* Render section
*/
const ListHeaderComponent = useMemo(() => (
<View style={{ marginHorizontal: 20, marginBottom: 12 }}>
<Text style={[{ textAlign: 'center', marginVertical: 6 }, defaultStyles.textHalfOpacity]}>
{t('total-download-size')}: {formatBytes(totalDownloadSize)}
</Text>
<Button
icon={TrashIcon}
title={t('delete-all-tracks')}
onPress={handleDeleteAllTracks}
disabled={!ids.length}
style={{ marginTop: 8 }}
/>
<Button
icon={ArrowClockwise}
title={t('retry-failed-downloads')}
onPress={handleRetryFailed}
disabled={failedIds.length === 0}
style={{ marginTop: 4 }}
/>
</View>
), [totalDownloadSize, defaultStyles, failedIds.length, handleRetryFailed, handleDeleteAllTracks, ids.length]);
const renderItem = useCallback<NonNullable<FlatListProps<EntityId>['renderItem']>>(({ item }) => (
<DownloadedTrack style={defaultStyles.border}>
<View style={{ marginRight: 12 }}>
<DownloadIcon trackId={item} />
</View>
<View style={{ flexShrink: 1, marginRight: 8 }}>
<Text style={[{ fontSize: 16, marginBottom: 4 }, defaultStyles.text]} numberOfLines={1}>
{tracks[item]?.Name}
</Text>
<Text style={[{ flexShrink: 1, fontSize: 11 }, defaultStyles.textHalfOpacity]} numberOfLines={1}>
{tracks[item]?.AlbumArtist} ({tracks[item]?.Album})
</Text>
</View>
<View style={{ marginLeft: 'auto', flexDirection: 'row', alignItems: 'center' }}>
{entities[item]?.isComplete && entities[item]?.size ? (
<Text style={[defaultStyles.textHalfOpacity, { marginRight: 6, fontSize: 12 }]}>
{formatBytes(entities[item]?.size || 0)}
</Text>
) : null}
<TouchableOpacity onPress={() => handleDelete(item)}>
<TrashIcon height={24} width={24} fill={THEME_COLOR} />
</TouchableOpacity>
{!entities[item]?.isComplete && (
<TouchableOpacity onPress={() => retryTrack(item)}>
<ArrowClockwise height={18} width={18} fill={THEME_COLOR} />
</TouchableOpacity>
)}
</View>
</DownloadedTrack>
), [entities, retryTrack, handleDelete, defaultStyles, tracks]);
// If no tracks have been downloaded, show a short message describing this
if (!ids.length) {
return (
<View style={{ margin: 24, flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text style={[{ textAlign: 'center'}, defaultStyles.textHalfOpacity]}>
{t('no-downloads')}
</Text>
</View>
);
}
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
data={ids}
style={{ flex: 1 }}
contentContainerStyle={{ flexGrow: 1 }}
ListHeaderComponent={ListHeaderComponent}
renderItem={renderItem}
/>
</SafeAreaView>
);
}
export default Downloads;

View File

@@ -1,26 +1,32 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { StackParams } from './types';
import { MusicStackParams } from './types';
import Albums from './stacks/Albums';
import Album from './stacks/Album';
import RecentAlbums from './stacks/RecentAlbums';
import Search from './stacks/Search';
import { THEME_COLOR } from 'CONSTANTS';
import { t } from '@localisation';
import useDefaultStyles from 'components/Colors';
import Playlists from './stacks/Playlists';
import Playlist from './stacks/Playlist';
const Stack = createStackNavigator<StackParams>();
const navigationOptions = {
headerTintColor: THEME_COLOR,
headerTitleStyle: { color: 'black' }
};
const Stack = createStackNavigator<MusicStackParams>();
function MusicStack() {
const defaultStyles = useDefaultStyles();
return (
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={navigationOptions}>
<Stack.Screen name="RecentAlbums" component={RecentAlbums} options={{ headerTitle: 'Recent Albums' }} />
<Stack.Screen name="Albums" component={Albums} />
<Stack.Screen name="Album" component={Album} />
<Stack.Screen name="Search" component={Search} />
<Stack.Navigator initialRouteName="RecentAlbums" screenOptions={{
headerTintColor: THEME_COLOR,
headerTitleStyle: defaultStyles.stackHeader
}}>
<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="Playlists" component={Playlists} options={{ headerTitle: t('playlists') }} />
<Stack.Screen name="Playlist" component={Playlist} options={{ headerTitle: t('playlist') }} />
<Stack.Screen name="Search" component={Search} options={{ headerTitle: t('search') }} />
</Stack.Navigator>
);
}

View File

@@ -1,96 +1,46 @@
import React, { useCallback, useEffect } from 'react';
import { StackParams } from '../types';
import { Text, ScrollView, Dimensions, Button, RefreshControl } from 'react-native';
import { useGetImage } from 'utility/JellyfinApi';
import styled, { css } from 'styled-components/native';
import { MusicStackParams } from '../types';
import { useRoute, RouteProp } from '@react-navigation/native';
import FastImage from 'react-native-fast-image';
import { useDispatch } from 'react-redux';
import { differenceInDays } from 'date-fns';
import { useTypedSelector } from 'store';
import TrackListView from './components/TrackListView';
import { fetchTracksByAlbum } from 'store/music/actions';
import { ALBUM_CACHE_AMOUNT_OF_DAYS, THEME_COLOR } from 'CONSTANTS';
import usePlayAlbum from 'utility/usePlayAlbum';
import usePlayTrack from 'utility/usePlayTrack';
import TouchableHandler from 'components/TouchableHandler';
import useCurrentTrack from 'utility/useCurrentTrack';
import { differenceInDays } from 'date-fns';
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
import { t } from '@localisation';
import { useDispatch } from 'react-redux';
type Route = RouteProp<StackParams, 'Album'>;
const Screen = Dimensions.get('screen');
const AlbumImage = styled(FastImage)`
border-radius: 10px;
width: ${Screen.width * 0.6}px;
height: ${Screen.width * 0.6}px;
margin: 10px auto;
`;
const TrackContainer = styled.View<{isPlaying: boolean}>`
padding: 15px;
border-bottom-width: 1px;
border-bottom-color: #eee;
flex-direction: row;
${props => props.isPlaying && css`
background-color: ${THEME_COLOR}16;
margin: 0 -20px;
padding: 15px 35px;
`}
`;
type Route = RouteProp<MusicStackParams, 'Album'>;
const Album: React.FC = () => {
// Retrieve state
const { params: { id } } = useRoute<Route>();
const tracks = useTypedSelector((state) => state.music.tracks.entities);
const album = useTypedSelector((state) => state.music.albums.entities[id]);
const isLoading = useTypedSelector((state) => state.music.tracks.isLoading);
// Retrieve helpers
const dispatch = useDispatch();
const getImage = useGetImage();
const playAlbum = usePlayAlbum();
const currentTrack = useCurrentTrack();
// Setup callbacks
const selectAlbum = useCallback(() => { playAlbum(id); }, [playAlbum, id]);
const selectTrack = usePlayTrack();
// Retrieve the album data from the store
const album = useTypedSelector((state) => state.music.albums.entities[id]);
const albumTracks = useTypedSelector((state) => state.music.tracks.byAlbum[id]);
// Define a function for refreshing this entity
const refresh = useCallback(() => { dispatch(fetchTracksByAlbum(id)); }, [id, dispatch]);
// Retrieve album tracks on load
// Auto-fetch the track data periodically
useEffect(() => {
if (!album?.lastRefreshed || differenceInDays(album?.lastRefreshed, new Date()) > ALBUM_CACHE_AMOUNT_OF_DAYS) {
refresh();
}
}, [album?.lastRefreshed, refresh]);
// GUARD: If there is no album, we cannot render a thing
if (!album) {
return null;
}
return (
<ScrollView
style={{ backgroundColor: '#f6f6f6', padding: 20, paddingBottom: 50 }}
refreshControl={
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
}
>
<AlbumImage source={{ uri: getImage(album?.Id) }} />
<Text style={{ fontSize: 36, fontWeight: 'bold' }} >{album?.Name}</Text>
<Text style={{ fontSize: 24, opacity: 0.5, marginBottom: 24 }}>{album?.AlbumArtist}</Text>
<Button title="Play Album" onPress={selectAlbum} color={THEME_COLOR} />
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
<TouchableHandler key={trackId} id={trackId} onPress={selectTrack}>
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false}>
<Text style={{ width: 20, opacity: 0.5, marginRight: 5 }}>
{tracks[trackId]?.IndexNumber}
</Text>
<Text>{tracks[trackId]?.Name}</Text>
</TrackContainer>
</TouchableHandler>
) : undefined}
</ScrollView>
<TrackListView
trackIds={albumTracks || []}
title={album?.Name}
artist={album?.AlbumArtist}
entityId={id}
refresh={refresh}
playButtonText={t('play-album')}
shuffleButtonText={t('shuffle-album')}
downloadButtonText={t('download-album')}
deleteButtonText={t('delete-album')}
/>
);
};

Some files were not shown because too many files have changed in this diff Show More