Compare commits
15 Commits
v2.0.2
...
feat/andro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4508f6cc1c | ||
|
|
56647cd7ab | ||
|
|
1648389ccc | ||
|
|
a532154ce0 | ||
|
|
74d82eb77a | ||
|
|
a8c0003fc1 | ||
|
|
ba805e061e | ||
|
|
cc14373575 | ||
|
|
943815e4a6 | ||
|
|
2f45f868c8 | ||
|
|
0a0c78f3d5 | ||
|
|
40ecfb08fb | ||
|
|
099bbebe38 | ||
|
|
a34b6c5114 | ||
|
|
7353b04dd1 |
3
.gitignore
vendored
@@ -72,4 +72,5 @@ certificates/
|
|||||||
sentry.properties
|
sentry.properties
|
||||||
|
|
||||||
screenshots
|
screenshots
|
||||||
fastlane/Preview.html
|
fastlane/Preview.html
|
||||||
|
fastlane/play-store-credentials.json
|
||||||
160
CHANGELOG.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
## [2.0.3](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.0.2...v2.0.3) (2023-02-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* improve album list scrolling performance ([099bbeb](https://github.com/leinelissen/jellyfin-audio-player/commit/099bbebe38942f2c72782e6c34ad3cea0876b291))
|
||||||
|
* prevent track indexes from overflowing ([a34b6c5](https://github.com/leinelissen/jellyfin-audio-player/commit/a34b6c51141cb3cd6058733ccb3323d75f40bbd5))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.0.2](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.0.1...v2.0.2) (2023-01-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* allow user-supplied CA certificates on Android ([ccfa68c](https://github.com/leinelissen/jellyfin-audio-player/commit/ccfa68c53045dfc1a7071d282da477a3ec6c9f60)), closes [#110](https://github.com/leinelissen/jellyfin-audio-player/issues/110)
|
||||||
|
* font colour for dark mode on input ([6885ae6](https://github.com/leinelissen/jellyfin-audio-player/commit/6885ae6216119155e86146c39ca502fa8a18183f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [2.0.1](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.0.0...v2.0.1) (2022-11-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* android and ios builds ([845b379](https://github.com/leinelissen/jellyfin-audio-player/commit/845b379e0983f012a2eda65350748307d4b74dca))
|
||||||
|
* Blur obscuring buttons on Android ([e0493c4](https://github.com/leinelissen/jellyfin-audio-player/commit/e0493c4a55157abff8fbb1eddeab331ac856feff))
|
||||||
|
* BlurView on Android ([b2bd211](https://github.com/leinelissen/jellyfin-audio-player/commit/b2bd211758f13a789294b98b5a129b07519ec3f8))
|
||||||
|
* Depcreated createReducer calls ([d072292](https://github.com/leinelissen/jellyfin-audio-player/commit/d072292008929aa53738bf69e91eb6925686687a))
|
||||||
|
* Ensure proper spacing in downloads screen ([cd10ddd](https://github.com/leinelissen/jellyfin-audio-player/commit/cd10ddd260c0a8d2b967248fe6dc0aeb09983e32))
|
||||||
|
* Input icon alignment on Android ([0ffc5b6](https://github.com/leinelissen/jellyfin-audio-player/commit/0ffc5b64894099d761451483fa7cd35e76446054))
|
||||||
|
* jumpy progress animations ([9807b0e](https://github.com/leinelissen/jellyfin-audio-player/commit/9807b0e920379ea646f6940d814cd2ed239a2054))
|
||||||
|
* margin on connection notice ([68de2ca](https://github.com/leinelissen/jellyfin-audio-player/commit/68de2ca80e3ba55489a34d9464af4f891093ffe6))
|
||||||
|
* Only show single line for tracks without artists or albums ([7ed389e](https://github.com/leinelissen/jellyfin-audio-player/commit/7ed389ead647c299be229b15fab47a8cc97be8c7))
|
||||||
|
* Remove any restrictions on bitrate and samplerate ([b41031e](https://github.com/leinelissen/jellyfin-audio-player/commit/b41031eeac9b5a9976b10a93d620bfd108c8d97c))
|
||||||
|
* Rename Jellyfin Audio Player to Fintunes in translation files ([0a7f6ab](https://github.com/leinelissen/jellyfin-audio-player/commit/0a7f6abf3e6af6f5684b63b0005868f250e687a2))
|
||||||
|
* screenshotting logic ([d4570b6](https://github.com/leinelissen/jellyfin-audio-player/commit/d4570b60aecdeae4ce8dedb63c511f359e9760cb))
|
||||||
|
* switch album id to demo instance ([9a1defb](https://github.com/leinelissen/jellyfin-audio-player/commit/9a1defbeef61a79addec4f71e0363e0b0271a111))
|
||||||
|
* use entire input box as touch area for focus ([87f992d](https://github.com/leinelissen/jellyfin-audio-player/commit/87f992d912f0846773a85d67b6f67a90fe1ac293))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Save App metadata in the repo ([9c8e474](https://github.com/leinelissen/jellyfin-audio-player/commit/9c8e474d51402f5e6fa24ab683cc86aa3e131552))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.7](https://github.com/leinelissen/jellyfin-audio-player/compare/v1.2.6...v1.2.7) (2022-08-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Allow FLAC playback ([5b54760](https://github.com/leinelissen/jellyfin-audio-player/commit/5b54760e4ee6620062ce0cc4c79daf81753f00ae))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.6](https://github.com/leinelissen/jellyfin-audio-player/compare/v1.2.6-beta.1...v1.2.6) (2022-08-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Peer dependency chain ([63bbbf2](https://github.com/leinelissen/jellyfin-audio-player/commit/63bbbf2719aa5d296a6ec99774f9bf1a1aa068d0))
|
||||||
|
* Remove unused imports ([c7f0d46](https://github.com/leinelissen/jellyfin-audio-player/commit/c7f0d46b410825765ab5d074469ec23d32ffd45d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.6-beta.1](https://github.com/leinelissen/jellyfin-audio-player/compare/v1.2.5...v1.2.6-beta.1) (2022-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.5](https://github.com/leinelissen/jellyfin-audio-player/compare/v1.2.4...v1.2.5) (2022-05-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Only pull Exoplayer from jcenter ([89d2984](https://github.com/leinelissen/jellyfin-audio-player/commit/89d29844b9821e1a42b3b60c43dc4c3078231d56))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Apply default text styles to ReText ([37ead0e](https://github.com/leinelissen/jellyfin-audio-player/commit/37ead0ec989a8b714fde1bcb6dd36e568c6e7e8c))
|
||||||
|
* Create new progress slider from scratch ([6efc8e7](https://github.com/leinelissen/jellyfin-audio-player/commit/6efc8e757c10c66019914f7561d075c3ecaf2f69))
|
||||||
|
* Implement colored blur backgrounds ([f48d248](https://github.com/leinelissen/jellyfin-audio-player/commit/f48d2481443850888a0bd1a1cf2604420e633b26))
|
||||||
|
* Tweak progress bar gestures ([b0961d3](https://github.com/leinelissen/jellyfin-audio-player/commit/b0961d3263d5f4ef3978fde748a6a277059cb0cb))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.4](https://github.com/leinelissen/jellyfin-audio-player/compare/v1.2.3...v1.2.4) (2022-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* No interaction on Android webview ([#59](https://github.com/leinelissen/jellyfin-audio-player/issues/59)) ([91eaa1d](https://github.com/leinelissen/jellyfin-audio-player/commit/91eaa1d864f66e1a6597809bd46c17907acc99ee))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.2.3](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.2.3...v1.2.3) (2022-01-16)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.3](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.2.2...v0.2.3) (2022-01-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.2](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.2.1...v0.2.2) (2022-01-03)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.1](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.2.0...v0.2.1) (2022-01-02)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.2.0](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.7...v0.2.0) (2022-01-02)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.7](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.6...v0.1.7) (2021-10-25)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.6](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.5...v0.1.6) (2021-04-25)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.5](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.4...v0.1.5) (2021-04-24)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.4](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.3...v0.1.4) (2021-04-03)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.3](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.2...v0.1.3) (2021-03-21)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.2](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.1...v0.1.2) (2021-03-09)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.1](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.1.0...v0.1.1) (2021-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [0.1.0](https://github.com/leinelissen/jellyfin-audio-player/compare/v1.0.0-beta3...v0.1.0) (2021-02-07)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.0.0-beta3](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.0.1-alpha1...v1.0.0-beta3) (2020-08-25)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.0.1-alpha1](https://github.com/leinelissen/jellyfin-audio-player/compare/v0.0.1-alpha0...v0.0.1-alpha1) (2020-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 0.0.1-alpha0 (2020-07-10)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
50
Gemfile.lock
@@ -1,7 +1,7 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.5)
|
CFPropertyList (3.0.6)
|
||||||
rexml
|
rexml
|
||||||
activesupport (6.1.6)
|
activesupport (6.1.6)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
@@ -9,7 +9,7 @@ GEM
|
|||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.3)
|
||||||
addressable (2.8.1)
|
addressable (2.8.4)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
algoliasearch (1.27.5)
|
algoliasearch (1.27.5)
|
||||||
httpclient (~> 2.8, >= 2.8.3)
|
httpclient (~> 2.8, >= 2.8.3)
|
||||||
@@ -17,16 +17,16 @@ GEM
|
|||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.660.0)
|
aws-partitions (1.743.0)
|
||||||
aws-sdk-core (3.167.0)
|
aws-sdk-core (3.171.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.59.0)
|
aws-sdk-kms (1.63.0)
|
||||||
aws-sdk-core (~> 3, >= 3.165.0)
|
aws-sdk-core (~> 3, >= 3.165.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.117.1)
|
aws-sdk-s3 (1.120.1)
|
||||||
aws-sdk-core (~> 3, >= 3.165.0)
|
aws-sdk-core (~> 3, >= 3.165.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.4)
|
||||||
@@ -86,8 +86,8 @@ GEM
|
|||||||
escape (0.0.4)
|
escape (0.0.4)
|
||||||
ethon (0.15.0)
|
ethon (0.15.0)
|
||||||
ffi (>= 1.15.0)
|
ffi (>= 1.15.0)
|
||||||
excon (0.94.0)
|
excon (0.99.0)
|
||||||
faraday (1.10.2)
|
faraday (1.10.3)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
faraday-em_synchrony (~> 1.0)
|
faraday-em_synchrony (~> 1.0)
|
||||||
faraday-excon (~> 1.1)
|
faraday-excon (~> 1.1)
|
||||||
@@ -116,7 +116,7 @@ GEM
|
|||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.6)
|
fastimage (2.2.6)
|
||||||
fastlane (2.211.0)
|
fastlane (2.212.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.8, < 3.0.0)
|
addressable (>= 2.8, < 3.0.0)
|
||||||
artifactory (~> 3.0)
|
artifactory (~> 3.0)
|
||||||
@@ -163,9 +163,9 @@ GEM
|
|||||||
fourflusher (2.3.1)
|
fourflusher (2.3.1)
|
||||||
fuzzy_match (2.0.4)
|
fuzzy_match (2.0.4)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-apis-androidpublisher_v3 (0.31.0)
|
google-apis-androidpublisher_v3 (0.38.0)
|
||||||
google-apis-core (>= 0.9.1, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-core (0.9.1)
|
google-apis-core (0.11.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@@ -174,10 +174,10 @@ GEM
|
|||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
rexml
|
||||||
webrick
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.16.0)
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
google-apis-core (>= 0.9.1, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-playcustomapp_v1 (0.12.0)
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
google-apis-core (>= 0.9.1, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-storage_v1 (0.19.0)
|
google-apis-storage_v1 (0.19.0)
|
||||||
google-apis-core (>= 0.9.0, < 2.a)
|
google-apis-core (>= 0.9.0, < 2.a)
|
||||||
google-cloud-core (1.6.0)
|
google-cloud-core (1.6.0)
|
||||||
@@ -185,7 +185,7 @@ GEM
|
|||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.6.0)
|
google-cloud-env (1.6.0)
|
||||||
faraday (>= 0.17.3, < 3.0)
|
faraday (>= 0.17.3, < 3.0)
|
||||||
google-cloud-errors (1.3.0)
|
google-cloud-errors (1.3.1)
|
||||||
google-cloud-storage (1.44.0)
|
google-cloud-storage (1.44.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
@@ -194,7 +194,7 @@ GEM
|
|||||||
google-cloud-core (~> 1.6)
|
google-cloud-core (~> 1.6)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (1.3.0)
|
googleauth (1.5.0)
|
||||||
faraday (>= 0.17.3, < 3.a)
|
faraday (>= 0.17.3, < 3.a)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@@ -207,11 +207,11 @@ GEM
|
|||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.10.0)
|
i18n (1.10.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jmespath (1.6.1)
|
jmespath (1.6.2)
|
||||||
json (2.6.2)
|
json (2.6.3)
|
||||||
jwt (2.5.0)
|
jwt (2.7.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.12.0)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
minitest (5.15.0)
|
minitest (5.15.0)
|
||||||
molinillo (0.8.0)
|
molinillo (0.8.0)
|
||||||
@@ -223,7 +223,7 @@ GEM
|
|||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
optparse (0.1.1)
|
optparse (0.1.1)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.6.0)
|
plist (3.7.0)
|
||||||
public_suffix (4.0.7)
|
public_suffix (4.0.7)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
representable (3.2.0)
|
representable (3.2.0)
|
||||||
@@ -242,7 +242,7 @@ GEM
|
|||||||
faraday (>= 0.17.5, < 3.a)
|
faraday (>= 0.17.5, < 3.a)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.8)
|
simctl (1.6.10)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
@@ -262,7 +262,7 @@ GEM
|
|||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8.2)
|
unf_ext (0.0.8.2)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (1.8.0)
|
||||||
webrick (1.7.0)
|
webrick (1.8.1)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.22.0)
|
xcodeproj (1.22.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
|
|||||||
@@ -138,9 +138,10 @@ android {
|
|||||||
applicationId "nl.moeilijkedingen.jellyfinaudioplayer"
|
applicationId "nl.moeilijkedingen.jellyfinaudioplayer"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 14
|
versionCode 15
|
||||||
versionName "2.0.2"
|
versionName "2.0.3"
|
||||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
if (isNewArchitectureEnabled()) {
|
if (isNewArchitectureEnabled()) {
|
||||||
// We configure the CMake build only if you decide to opt-in for the New Architecture.
|
// We configure the CMake build only if you decide to opt-in for the New Architecture.
|
||||||
@@ -217,6 +218,14 @@ android {
|
|||||||
keyAlias 'androiddebugkey'
|
keyAlias 'androiddebugkey'
|
||||||
keyPassword 'android'
|
keyPassword 'android'
|
||||||
}
|
}
|
||||||
|
release {
|
||||||
|
if (project.hasProperty('FINTUNES_UPLOAD_STORE_FILE')) {
|
||||||
|
storeFile file(FINTUNES_UPLOAD_STORE_FILE)
|
||||||
|
storePassword FINTUNES_UPLOAD_STORE_PASSWORD
|
||||||
|
keyAlias FINTUNES_UPLOAD_KEY_ALIAS
|
||||||
|
keyPassword FINTUNES_UPLOAD_KEY_PASSWORD
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
@@ -225,7 +234,11 @@ android {
|
|||||||
release {
|
release {
|
||||||
// Caution! In production, you need to generate your own keystore file.
|
// Caution! In production, you need to generate your own keystore file.
|
||||||
// see https://reactnative.dev/docs/signed-apk-android.
|
// see https://reactnative.dev/docs/signed-apk-android.
|
||||||
signingConfig signingConfigs.debug
|
if (project.hasProperty('FINTUNES_UPLOAD_STORE_FILE')) {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
} else {
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
minifyEnabled enableProguardInReleaseBuilds
|
minifyEnabled enableProguardInReleaseBuilds
|
||||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||||
}
|
}
|
||||||
@@ -269,6 +282,21 @@ dependencies {
|
|||||||
exclude group:'com.facebook.flipper'
|
exclude group:'com.facebook.flipper'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fastlane screengrab, falcon is a dependency of screengrab
|
||||||
|
androidTestImplementation 'com.jraska:falcon:2.2.0'
|
||||||
|
androidTestImplementation "tools.fastlane:screengrab:2.1.0"
|
||||||
|
// Espresso dependencies
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
// Hamcrest library
|
||||||
|
androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
|
||||||
|
|
||||||
|
// Core library
|
||||||
|
androidTestImplementation 'androidx.test:core:1.4.0'
|
||||||
|
|
||||||
|
// AndroidJUnitRunner and JUnit Rules
|
||||||
|
androidTestImplementation 'androidx.test:runner:1.4.0'
|
||||||
|
androidTestImplementation 'androidx.test:rules:1.4.0'
|
||||||
|
|
||||||
if (enableHermes) {
|
if (enableHermes) {
|
||||||
//noinspection GradleDynamicVersion
|
//noinspection GradleDynamicVersion
|
||||||
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
|
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
|
||||||
|
|||||||
105
android/app/src/androidTest/java/ScreenshotTest.java
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package nl.moeilijkedingen.jellyfinaudioplayer;
|
||||||
|
|
||||||
|
import androidx.test.rule.ActivityTestRule;
|
||||||
|
|
||||||
|
import nl.moeilijkedingen.jellyfinaudioplayer.R;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import tools.fastlane.screengrab.Screengrab;
|
||||||
|
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
|
||||||
|
import tools.fastlane.screengrab.cleanstatusbar.BluetoothState;
|
||||||
|
import tools.fastlane.screengrab.cleanstatusbar.CleanStatusBar;
|
||||||
|
import tools.fastlane.screengrab.cleanstatusbar.MobileDataType;
|
||||||
|
import tools.fastlane.screengrab.locale.LocaleTestRule;
|
||||||
|
|
||||||
|
import androidx.test.espresso.NoMatchingViewException;
|
||||||
|
|
||||||
|
import static androidx.test.espresso.Espresso.onView;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.click;
|
||||||
|
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||||
|
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.AllOf.allOf;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class ScreenshotTest {
|
||||||
|
@ClassRule
|
||||||
|
public static final LocaleTestRule localeTestRule = new LocaleTestRule();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class);
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeAll() {
|
||||||
|
Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
|
||||||
|
CleanStatusBar.enableWithDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterAll() {
|
||||||
|
CleanStatusBar.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Custom wait function. In order to make sure each button press yields a
|
||||||
|
desirable screen, we use the wait function to delay further actions until
|
||||||
|
the current one has achieved its purpose.
|
||||||
|
|
||||||
|
`duration` indicates the amount of milli-seconds to wait. The value of
|
||||||
|
`duration` is acquired by emperical trial-and-error.
|
||||||
|
*/
|
||||||
|
public void wait(int duration) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(duration);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void inputText(Integer id, String text) {
|
||||||
|
try {
|
||||||
|
onView(allOf(withId(id))).perform(typeText(text));
|
||||||
|
} catch (NoMatchingViewException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTakeScreenshot() {
|
||||||
|
System.out.println("AVAILABLE IDS:" + Arrays.toString(R.id.class.getFields()));
|
||||||
|
// wait(10000);
|
||||||
|
|
||||||
|
// Screengrab.screenshot("04RecentAlbums");
|
||||||
|
// onView(allOf(withId(R.id.all_albums))).perform(click());
|
||||||
|
// wait(5000);
|
||||||
|
// Screengrab.screenshot("05AlbumsScreen");
|
||||||
|
|
||||||
|
// onView(allOf(withId(R.id.search_tab))).perform(click());
|
||||||
|
// wait(5000);
|
||||||
|
// onView(allOf(withId(R.id.search_input_container))).perform(click());
|
||||||
|
// wait(5000);
|
||||||
|
// onView(allOf(withId(R.id.search_input_textinput))).perform(typeText("bicep"));
|
||||||
|
// wait(5000);
|
||||||
|
|
||||||
|
// onView(allOf(withId(R.id.search_result_a644f8d23821601d2feb86ddae5e64f4))).perform(click());
|
||||||
|
// wait(5000);
|
||||||
|
// Screengrab.screenshot("02AlbumScreen");
|
||||||
|
|
||||||
|
// onView(allOf(withId(R.id.play_album))).perform(click());
|
||||||
|
// wait(5000);
|
||||||
|
// onView(allOf(withId(R.id.open_player_modal))).perform(click());
|
||||||
|
// wait(5000);
|
||||||
|
// Screengrab.screenshot("01PlayModal");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,18 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
<uses-permission android:name="android.permission.DUMP"/>
|
||||||
|
|
||||||
|
<!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
|
||||||
|
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
|
||||||
|
<!-- Allows for storing and retrieving screenshots -->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<!-- Allows changing locales -->
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:networkSecurityConfig="@xml/react_native_config"
|
android:networkSecurityConfig="@xml/react_native_config"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m
|
||||||
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
|
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||||
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
|
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
|
||||||
apple_id("lei@moeilijkedingen.nl")
|
apple_id("lei@moeilijkedingen.nl")
|
||||||
team_id("238P3C58WC")
|
team_id("238P3C58WC")
|
||||||
|
json_key_file("./fastlane/play-store-credentials.json")
|
||||||
@@ -42,9 +42,15 @@ platform :ios do
|
|||||||
workspace: "ios/Fintunes.xcworkspace",
|
workspace: "ios/Fintunes.xcworkspace",
|
||||||
export_method: "app-store",
|
export_method: "app-store",
|
||||||
)
|
)
|
||||||
upload_to_testflight(
|
upload_to_testflight()
|
||||||
submit_for_review: true,
|
end
|
||||||
automatic_release: true,
|
|
||||||
|
lane :build do
|
||||||
|
build_app(
|
||||||
|
scheme: "Fintunes",
|
||||||
|
output_directory: "build",
|
||||||
|
workspace: "ios/Fintunes.xcworkspace",
|
||||||
|
export_method: "app-store",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -104,8 +110,36 @@ platform :android do
|
|||||||
gradle_file: "android/app/build.gradle"
|
gradle_file: "android/app/build.gradle"
|
||||||
)
|
)
|
||||||
gradle(
|
gradle(
|
||||||
task: "assembleRelease",
|
task: "assemble",
|
||||||
|
build_type: "Release",
|
||||||
project_dir: "android"
|
project_dir: "android"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
lane :release do
|
||||||
|
gradle(
|
||||||
|
task: "bundle",
|
||||||
|
build_type: 'Release',
|
||||||
|
project_dir: "android"
|
||||||
|
)
|
||||||
|
upload_to_play_store
|
||||||
|
end
|
||||||
|
|
||||||
|
lane :screenshots do
|
||||||
|
gradle(task: 'clean', project_dir: 'android/')
|
||||||
|
gradle(
|
||||||
|
task: 'assemble',
|
||||||
|
build_type: 'Debug',
|
||||||
|
project_dir: 'android/',
|
||||||
|
)
|
||||||
|
gradle(
|
||||||
|
task: 'assemble',
|
||||||
|
build_type: 'AndroidTest',
|
||||||
|
project_dir: 'android/',
|
||||||
|
)
|
||||||
|
capture_android_screenshots(
|
||||||
|
app_apk_path: "android/app/build/outputs/apk/debug/app-debug.apk",
|
||||||
|
tests_apk_path: "android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### ios build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane ios build
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### ios screenshots
|
### ios screenshots
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -52,6 +60,22 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
|||||||
|
|
||||||
Generate beta build
|
Generate beta build
|
||||||
|
|
||||||
|
### android release
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android release
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### android screenshots
|
||||||
|
|
||||||
|
```sh
|
||||||
|
[bundle exec] fastlane android screenshots
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||||
|
|||||||
1
fastlane/metadata/android/en-GB/full_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fintunes is a streaming audio player for the Jellyfin media system. It features a gorgeous interface that allows you to play your favourite music with ease. You can search your entire library for any track, or just take it easy with a playlist that you've created earlier in Jellyfin. All tracks are streamed directly at the highest quality from your Jellyfin library. Streaming not always an option? Any track in your Jellyfin library can be downloaded and played offline.
|
||||||
1
fastlane/metadata/android/en-GB/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Streaming audio player for Jellyfin
|
||||||
1
fastlane/metadata/android/en-GB/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Fintunes
|
||||||
0
fastlane/metadata/android/en-GB/video.txt
Normal file
|
After Width: | Height: | Size: 746 KiB |
|
After Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 366 KiB |
@@ -606,7 +606,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 51;
|
CURRENT_PROJECT_VERSION = 53;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
@@ -643,7 +643,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 51;
|
CURRENT_PROJECT_VERSION = 53;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
INFOPLIST_FILE = Fintunes/Info.plist;
|
INFOPLIST_FILE = Fintunes/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
@@ -799,7 +799,7 @@
|
|||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 51;
|
CURRENT_PROJECT_VERSION = 53;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@@ -832,7 +832,7 @@
|
|||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 51;
|
CURRENT_PROJECT_VERSION = 53;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = 238P3C58WC;
|
DEVELOPMENT_TEAM = 238P3C58WC;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.0.2</string>
|
<string>2.0.3</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>51</string>
|
<string>53</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
|||||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "fintunes",
|
"name": "fintunes",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "fintunes",
|
"name": "fintunes",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-async-storage/async-storage": "^1.17.11",
|
"@react-native-async-storage/async-storage": "^1.17.11",
|
||||||
"@react-native-community/blur": "^4.3.0",
|
"@react-native-community/blur": "^4.3.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fintunes",
|
"name": "fintunes",
|
||||||
"version": "2.0.2",
|
"version": "2.0.3",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
BIN
src/assets/images/empty-album-dark.png
Normal file
|
After Width: | Height: | Size: 1005 KiB |
BIN
src/assets/images/empty-album-light.png
Normal file
|
After Width: | Height: | Size: 926 KiB |
@@ -2,7 +2,7 @@ import { BlurView, BlurViewProps } from '@react-native-community/blur';
|
|||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import React, { PropsWithChildren } from 'react';
|
import React, { PropsWithChildren } from 'react';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { ColorSchemeName, Platform, StyleSheet, useColorScheme } from 'react-native';
|
import { ColorSchemeName, Platform, StyleSheet, View, useColorScheme } from 'react-native';
|
||||||
|
|
||||||
const majorPlatformVersion = typeof Platform.Version === 'string' ? parseInt(Platform.Version, 10) : Platform.Version;
|
const majorPlatformVersion = typeof Platform.Version === 'string' ? parseInt(Platform.Version, 10) : Platform.Version;
|
||||||
|
|
||||||
@@ -108,14 +108,8 @@ export function ColoredBlurView(props: PropsWithChildren<BlurViewProps>) {
|
|||||||
: scheme === 'dark' ? 'extraDark' : 'xlight'
|
: scheme === 'dark' ? 'extraDark' : 'xlight'
|
||||||
} />
|
} />
|
||||||
) : (
|
) : (
|
||||||
<BlurView
|
<View {...props} style={[ props.style, {
|
||||||
{...props}
|
backgroundColor: scheme === 'light' ? '#f6f6f6f6' : '#333333f6',
|
||||||
blurType={scheme === 'dark' ? 'dark' : 'light'}
|
} ]} />
|
||||||
blurAmount={10}
|
|
||||||
style={[ props.style, {
|
|
||||||
backgroundColor: scheme === 'light' ? '#f6f6f6bb' : '#333333bb',
|
|
||||||
borderRadius: 8
|
|
||||||
} ]}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Dimensions, ViewProps } from 'react-native';
|
import { Dimensions, useColorScheme, ViewProps } from 'react-native';
|
||||||
import { Canvas, Blur, Image as SkiaImage, useImage, Offset, Mask, RoundedRect, Shadow } from '@shopify/react-native-skia';
|
import { Canvas, Blur, Image as SkiaImage, useImage, Offset, Mask, RoundedRect, Shadow } from '@shopify/react-native-skia';
|
||||||
import useDefaultStyles from './Colors';
|
import useDefaultStyles from './Colors';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
@@ -45,14 +45,18 @@ function CoverImage({
|
|||||||
src,
|
src,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const defaultStyles = useDefaultStyles();
|
const defaultStyles = useDefaultStyles();
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
|
||||||
const image = useImage(src || '');
|
const image = useImage(src || null);
|
||||||
|
const fallback = useImage(colorScheme === 'light' ? require('assets/images/empty-album-light.png') : require('assets/images/empty-album-dark.png'));
|
||||||
const { canvasSize, imageSize } = useMemo(() => {
|
const { canvasSize, imageSize } = useMemo(() => {
|
||||||
const imageSize = Screen.width - margin;
|
const imageSize = Screen.width - margin;
|
||||||
const canvasSize = imageSize + blurRadius * 2;
|
const canvasSize = imageSize + blurRadius * 2;
|
||||||
return { imageSize, canvasSize };
|
return { imageSize, canvasSize };
|
||||||
}, [blurRadius, margin]);
|
}, [blurRadius, margin]);
|
||||||
|
|
||||||
|
const skiaImage = useMemo(() => (image || fallback), [image, fallback]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size={imageSize} style={style}>
|
<Container size={imageSize} style={style}>
|
||||||
<BlurContainer size={canvasSize} offset={blurRadius}>
|
<BlurContainer size={canvasSize} offset={blurRadius}>
|
||||||
@@ -63,18 +67,16 @@ function CoverImage({
|
|||||||
<Shadow dx={0} dy={8} blur={16} color="#0000000d" />
|
<Shadow dx={0} dy={8} blur={16} color="#0000000d" />
|
||||||
<Shadow dx={0} dy={16} blur={32} color="#0000000d" />
|
<Shadow dx={0} dy={16} blur={32} color="#0000000d" />
|
||||||
</RoundedRect>
|
</RoundedRect>
|
||||||
{image ? (
|
{skiaImage ? (
|
||||||
<>
|
<>
|
||||||
<SkiaImage image={image} width={imageSize} height={imageSize} opacity={opacity}>
|
<SkiaImage image={skiaImage} width={imageSize} height={imageSize} opacity={opacity}>
|
||||||
<Offset x={blurRadius} y={blurRadius} />
|
<Offset x={blurRadius} y={blurRadius} />
|
||||||
<Blur blur={blurRadius / 2} />
|
<Blur blur={blurRadius / 2} />
|
||||||
</SkiaImage>
|
</SkiaImage>
|
||||||
<Mask mask={<RoundedRect width={imageSize} height={imageSize} x={blurRadius} y={blurRadius} r={radius} />}>
|
<Mask mask={<RoundedRect width={imageSize} height={imageSize} x={blurRadius} y={blurRadius} r={radius} />}>
|
||||||
{image ? (
|
<SkiaImage image={skiaImage} width={imageSize} height={imageSize}>
|
||||||
<SkiaImage image={image} width={imageSize} height={imageSize}>
|
<Offset x={blurRadius} y={blurRadius} />
|
||||||
<Offset x={blurRadius} y={blurRadius} />
|
</SkiaImage>
|
||||||
</SkiaImage>
|
|
||||||
) : null}
|
|
||||||
</Mask>
|
</Mask>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
import React, { useCallback, useEffect, useRef, ReactText, useMemo } from 'react';
|
||||||
import { useGetImage } from 'utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { MusicNavigationProp } from '../types';
|
import { MusicNavigationProp } from '../types';
|
||||||
import { SafeAreaView, SectionList, View } from 'react-native';
|
import { SafeAreaView, SectionList, View } from 'react-native';
|
||||||
@@ -20,22 +20,6 @@ import { ShadowWrapper } from 'components/Shadow';
|
|||||||
|
|
||||||
const HeadingHeight = 50;
|
const HeadingHeight = 50;
|
||||||
|
|
||||||
interface VirtualizedItemInfo {
|
|
||||||
section: SectionedId,
|
|
||||||
// Key of the section or combined key for section + item
|
|
||||||
key: string,
|
|
||||||
// Relative index within the section
|
|
||||||
index: number,
|
|
||||||
// True if this is the section header
|
|
||||||
header?: boolean,
|
|
||||||
leadingItem?: EntityId,
|
|
||||||
leadingSection?: SectionedId,
|
|
||||||
trailingItem?: EntityId,
|
|
||||||
trailingSection?: SectionedId,
|
|
||||||
}
|
|
||||||
|
|
||||||
type VirtualizedSectionList = { _subExtractor: (index: number) => VirtualizedItemInfo };
|
|
||||||
|
|
||||||
function generateSection({ section }: { section: SectionedId }) {
|
function generateSection({ section }: { section: SectionedId }) {
|
||||||
return (
|
return (
|
||||||
<SectionHeading label={section.label} key={section.label} />
|
<SectionHeading label={section.label} key={section.label} />
|
||||||
@@ -105,42 +89,52 @@ const Albums: React.FC = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigation = useNavigation<MusicNavigationProp>();
|
const navigation = useNavigation<MusicNavigationProp>();
|
||||||
const getImage = useGetImage();
|
const getImage = useGetImage();
|
||||||
const listRef = useRef<SectionList<EntityId>>(null);
|
const listRef = useRef<SectionList<EntityId[]>>(null);
|
||||||
|
|
||||||
const getItemLayout = useCallback((data: SectionedId[] | null, index: number): { offset: number, length: number, index: number } => {
|
// Create an array that computes all the height data for the entire list in
|
||||||
// We must wait for the ref to become available before we can use the
|
// advance. We can then use this pre-computed data to respond to
|
||||||
// native item retriever in VirtualizedSectionList
|
// `getItemLayout` calls, without having to compute things in place (and
|
||||||
if (!listRef.current) {
|
// fail horribly).
|
||||||
return { offset: 0, length: 0, index };
|
// This approach was inspired by https://gist.github.com/RaphBlanchet/472ed013e05398c083caae6216b598b5
|
||||||
}
|
const itemLayouts = useMemo(() => {
|
||||||
|
// Create an array in which we will store all possible outputs for
|
||||||
|
// `getItemLayout`. We will loop through each potential album and add
|
||||||
|
// items that will be in the list
|
||||||
|
const layouts: Array<{ length: number; offset: number; index: number }> = [];
|
||||||
|
|
||||||
|
// Keep track of both the index of items and the offset (in pixels) from
|
||||||
|
// the top
|
||||||
|
let index = 0;
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
// Retrieve the right item info
|
// Loop through each individual section (i.e. alphabet letter) and add
|
||||||
// @ts-ignore
|
// all items in that particular section.
|
||||||
const wrapperListRef = (listRef.current?._wrapperListRef) as VirtualizedSectionList;
|
sections.forEach((section) => {
|
||||||
const info: VirtualizedItemInfo = wrapperListRef._subExtractor(index);
|
// Each section starts with a header, so we'll need to add the item,
|
||||||
const { index: itemIndex, header, key } = info;
|
// as well as the offset.
|
||||||
const sectionIndex = parseInt(key.split(':')[0]);
|
layouts[index] = ({ length: HeadingHeight, offset, index });
|
||||||
|
index++;
|
||||||
|
offset += HeadingHeight;
|
||||||
|
|
||||||
// We can then determine the "length" (=height) of this item. Header items
|
// Then, loop through all the rows (sets of two albums) and add
|
||||||
// end up with an itemIndex of -1, thus are easy to identify.
|
// items for those as well.
|
||||||
const length = header ? 50 : (itemIndex % 2 === 0 ? AlbumHeight : 0);
|
section.data.forEach(() => {
|
||||||
|
layouts[index] = ({ length: AlbumHeight, offset, index });
|
||||||
// We'll also need to account for any unevenly-ended lists up until the
|
index++;
|
||||||
// current item.
|
offset += AlbumHeight;
|
||||||
const previousRows = data?.filter((row, i) => i < sectionIndex)
|
});
|
||||||
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
|
|
||||||
|
|
||||||
// We must also calcuate the offset, total distance from the top of the
|
// The way SectionList works is that you get an item for a
|
||||||
// screen. First off, we'll account for each sectionIndex that is shown up
|
// SectionHeader and a SectionFooter, no matter if you've specified
|
||||||
// until now. This only includes the heading for the current section if the
|
// whether you want them or not. Thus, we will need to add an empty
|
||||||
// item is not the section header
|
// footer as an item, so that we don't mismatch our indexes
|
||||||
const headingOffset = HeadingHeight * (header ? sectionIndex : sectionIndex + 1);
|
layouts[index] = { length: 0, offset, index };
|
||||||
const currentRows = itemIndex > 1 ? Math.ceil((itemIndex + 1) / 2) : 0;
|
index++;
|
||||||
const itemOffset = AlbumHeight * (previousRows + currentRows);
|
});
|
||||||
const offset = headingOffset + itemOffset;
|
|
||||||
|
// Then, store and memoize the output
|
||||||
return { index, length, offset };
|
return layouts;
|
||||||
}, [listRef]);
|
}, [sections]);
|
||||||
|
|
||||||
// Set callbacks
|
// Set callbacks
|
||||||
const retrieveData = useCallback(() => dispatch(fetchAllAlbums()), [dispatch]);
|
const retrieveData = useCallback(() => dispatch(fetchAllAlbums()), [dispatch]);
|
||||||
@@ -148,30 +142,19 @@ const Albums: React.FC = () => {
|
|||||||
const selectLetter = useCallback((sectionIndex: number) => {
|
const selectLetter = useCallback((sectionIndex: number) => {
|
||||||
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
|
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
|
||||||
}, [listRef]);
|
}, [listRef]);
|
||||||
const generateItem = useCallback(({ item, index, section }: { item: EntityId, index: number, section: SectionedId }) => {
|
const generateItem = useCallback(({ item }: { item: EntityId[] }) => {
|
||||||
if (index % 2 === 1) {
|
|
||||||
return <View key={item} />;
|
|
||||||
}
|
|
||||||
const nextItem = section.data[index + 1];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
|
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item.join('-')}>
|
||||||
<GeneratedAlbumItem
|
{item.map((id) => (
|
||||||
id={item}
|
|
||||||
imageUrl={getImage(item as string)}
|
|
||||||
name={albums[item]?.Name || ''}
|
|
||||||
artist={albums[item]?.AlbumArtist || ''}
|
|
||||||
onPress={selectAlbum}
|
|
||||||
/>
|
|
||||||
{albums[nextItem] &&
|
|
||||||
<GeneratedAlbumItem
|
<GeneratedAlbumItem
|
||||||
id={nextItem}
|
key={id}
|
||||||
imageUrl={getImage(nextItem as string)}
|
id={id}
|
||||||
name={albums[nextItem]?.Name || ''}
|
imageUrl={getImage(id as string)}
|
||||||
artist={albums[nextItem]?.AlbumArtist || ''}
|
name={albums[id]?.Name || ''}
|
||||||
|
artist={albums[id]?.AlbumArtist || ''}
|
||||||
onPress={selectAlbum}
|
onPress={selectAlbum}
|
||||||
/>
|
/>
|
||||||
}
|
))}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}, [albums, getImage, selectAlbum]);
|
}, [albums, getImage, selectAlbum]);
|
||||||
@@ -191,9 +174,9 @@ const Albums: React.FC = () => {
|
|||||||
sections={sections}
|
sections={sections}
|
||||||
refreshing={isLoading}
|
refreshing={isLoading}
|
||||||
onRefresh={retrieveData}
|
onRefresh={retrieveData}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={(_, i) => itemLayouts[i] ?? { length: 0, offset: 0, index: i }}
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
keyExtractor={(item) => item as string}
|
keyExtractor={(item) => item.join('-')}
|
||||||
renderSectionHeader={generateSection}
|
renderSectionHeader={generateSection}
|
||||||
renderItem={generateItem}
|
renderItem={generateItem}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import styled from 'styled-components/native';
|
|
||||||
import FastImage from 'react-native-fast-image';
|
|
||||||
import { Dimensions } from 'react-native';
|
|
||||||
|
|
||||||
const Screen = Dimensions.get('screen');
|
|
||||||
export const AlbumWidth = Screen.width / 2 - 24;
|
|
||||||
export const AlbumHeight = AlbumWidth + 40;
|
|
||||||
export const CoverSize = AlbumWidth - 16;
|
|
||||||
|
|
||||||
export const AlbumItem = styled.View`
|
|
||||||
width: ${AlbumWidth}px;
|
|
||||||
height: ${AlbumHeight}px;
|
|
||||||
padding: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const AlbumImage = styled(FastImage)`
|
|
||||||
border-radius: 10px;
|
|
||||||
width: ${CoverSize}px;
|
|
||||||
height: ${CoverSize}px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default AlbumImage;
|
|
||||||
39
src/screens/Music/stacks/components/AlbumImage.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
import FastImage, { FastImageProps } from 'react-native-fast-image';
|
||||||
|
import { Dimensions, useColorScheme } from 'react-native';
|
||||||
|
|
||||||
|
const Screen = Dimensions.get('screen');
|
||||||
|
export const AlbumWidth = Screen.width / 2 - 24;
|
||||||
|
export const AlbumHeight = AlbumWidth + 40;
|
||||||
|
export const CoverSize = AlbumWidth - 16;
|
||||||
|
|
||||||
|
export const AlbumItem = styled.View`
|
||||||
|
width: ${AlbumWidth}px;
|
||||||
|
height: ${AlbumHeight}px;
|
||||||
|
padding: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled(FastImage)`
|
||||||
|
border-radius: 10px;
|
||||||
|
width: ${CoverSize}px;
|
||||||
|
height: ${CoverSize}px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function AlbumImage(props: FastImageProps) {
|
||||||
|
const [hasError, setError] = useState(false);
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
|
||||||
|
if (!props.source || hasError) {
|
||||||
|
return (
|
||||||
|
<Container source={colorScheme === 'light' ? require('assets/images/empty-album-light.png') : require('assets/images/empty-album-dark.png')} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container {...props} onError={() => setError(true)} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumImage;
|
||||||
@@ -28,8 +28,7 @@ import ticksToDuration from 'utility/ticksToDuration';
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
index: {
|
index: {
|
||||||
width: 16,
|
marginRight: 12
|
||||||
marginRight: 8
|
|
||||||
},
|
},
|
||||||
activeText: {
|
activeText: {
|
||||||
color: THEME_COLOR,
|
color: THEME_COLOR,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const selectAlbumsByArtist = createSelector(
|
|||||||
albumsByArtist,
|
albumsByArtist,
|
||||||
);
|
);
|
||||||
|
|
||||||
export type SectionedId = SectionListData<EntityId>;
|
export type SectionedId = SectionListData<EntityId[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits a set of albums into a list that is split by alphabet letters
|
* Splits a set of albums into a list that is split by alphabet letters
|
||||||
@@ -58,13 +58,26 @@ export type SectionedId = SectionListData<EntityId>;
|
|||||||
function splitAlbumsByAlphabet(state: AppState['music']['albums']): SectionedId[] {
|
function splitAlbumsByAlphabet(state: AppState['music']['albums']): SectionedId[] {
|
||||||
const { entities: albums } = state;
|
const { entities: albums } = state;
|
||||||
const albumIds = albumsByArtist(state);
|
const albumIds = albumsByArtist(state);
|
||||||
const sections: SectionedId[] = ALPHABET_LETTERS.split('').map((l) => ({ label: l, data: [] }));
|
const sections: SectionedId[] = ALPHABET_LETTERS.split('').map((l) => ({ label: l, data: [[]] }));
|
||||||
|
|
||||||
albumIds.forEach((id) => {
|
albumIds.forEach((id) => {
|
||||||
|
// Retrieve the album letter and corresponding letter index
|
||||||
const album = albums[id];
|
const album = albums[id];
|
||||||
const letter = album?.AlbumArtist?.toUpperCase().charAt(0);
|
const letter = album?.AlbumArtist?.toUpperCase().charAt(0);
|
||||||
const index = letter ? ALPHABET_LETTERS.indexOf(letter) : 26;
|
const index = letter ? ALPHABET_LETTERS.indexOf(letter) : 26;
|
||||||
(sections[index >= 0 ? index : 26].data as Array<EntityId>).push(id);
|
|
||||||
|
// Then find the current row in this section (note that albums are
|
||||||
|
// grouped in pairs so we can render them more easily).
|
||||||
|
const section = sections[index >= 0 ? index : 26];
|
||||||
|
const row = section.data.length - 1;
|
||||||
|
|
||||||
|
// Add the album to the row
|
||||||
|
section.data[row].push(id);
|
||||||
|
|
||||||
|
// GUARD: Check if the row is overflowing. If so, add a new row.
|
||||||
|
if (section.data[row].length >= 2) {
|
||||||
|
(section.data as EntityId[][]).push([]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return sections;
|
return sections;
|
||||||
|
|||||||