Compare commits

...

18 Commits

Author SHA1 Message Date
Lei Nelissen
531c6f708d chore: release v2.0.4 2023-04-11 18:39:37 +02:00
Lei Nelissen
56647cd7ab chore: add Android screenshots 2023-04-11 18:34:12 +02:00
Lei Nelissen
1648389ccc fix: disable BlurView on Android as it crashes the app 2023-04-11 18:27:55 +02:00
Lei Nelissen
a532154ce0 fix: use debug signing config when not having a keystore 2023-04-11 10:48:24 +02:00
Lei Nelissen
74d82eb77a fix: only set signingConfig to release when a keystore is available 2023-04-10 17:42:51 +02:00
Lei Nelissen
a8c0003fc1 fix: linter issue 2023-04-10 17:15:32 +02:00
Lei Nelissen
ba805e061e feat: Add base Android content for F-Droid and Play Store 2023-04-10 17:10:53 +02:00
Lei Nelissen
cc14373575 feat: setup Fastlane for Google Play Store 2023-04-10 17:10:12 +02:00
Lei Nelissen
943815e4a6 chore: update fastlane 2023-04-10 17:09:24 +02:00
Lei Nelissen
2f45f868c8 fix: linting issue 2023-03-08 10:09:59 +01:00
Lei Nelissen
0a0c78f3d5 feat: add fallback images when album cover isn't available 2023-03-07 23:03:09 +01:00
Lei Nelissen
40ecfb08fb chore: release v2.0.3 2023-02-28 10:25:22 +01:00
Lei Nelissen
099bbebe38 fix: improve album list scrolling performance 2023-02-28 10:08:24 +01:00
Lei Nelissen
a34b6c5114 fix: prevent track indexes from overflowing 2023-02-10 17:08:12 +01:00
Lei Nelissen
7353b04dd1 chore: add CHANGELOG 2023-01-10 23:25:38 +01:00
Lei Nelissen
a2c1a82ebb chore: relase v2.0.2 2023-01-10 23:08:02 +01:00
Lei Nelissen
ccfa68c530 fix: allow user-supplied CA certificates on Android
fixes #110
2023-01-10 22:35:02 +01:00
Lei Nelissen
6885ae6216 fix: font colour for dark mode on input 2023-01-10 22:07:53 +01:00
34 changed files with 425 additions and 163 deletions

3
.gitignore vendored
View File

@@ -72,4 +72,5 @@ certificates/
sentry.properties
screenshots
fastlane/Preview.html
fastlane/Preview.html
fastlane/play-store-credentials.json

180
CHANGELOG.md Normal file
View File

@@ -0,0 +1,180 @@
## [2.0.4](https://github.com/leinelissen/jellyfin-audio-player/compare/v2.0.3...v2.0.4) (2023-04-11)
### Bug Fixes
* disable BlurView on Android as it crashes the app ([1648389](https://github.com/leinelissen/jellyfin-audio-player/commit/1648389ccce088e6836bcad31bd5c3b7cb996a78))
* linter issue ([a8c0003](https://github.com/leinelissen/jellyfin-audio-player/commit/a8c0003fc13cb7d4778f65e8702b1c3c5fd1cc59))
* linting issue ([2f45f86](https://github.com/leinelissen/jellyfin-audio-player/commit/2f45f868c8cc8a7f4308282b672d1d487f480c0a))
* only set signingConfig to release when a keystore is available ([74d82eb](https://github.com/leinelissen/jellyfin-audio-player/commit/74d82eb77a412ba84d0820abbad84ac304c62611))
* use debug signing config when not having a keystore ([a532154](https://github.com/leinelissen/jellyfin-audio-player/commit/a532154ce023ba2eecbbc3c8d7bbe08bcca0cd57))
### Features
* Add base Android content for F-Droid and Play Store ([ba805e0](https://github.com/leinelissen/jellyfin-audio-player/commit/ba805e061e56d719b18cfd8a6bafccf9174110b8))
* add fallback images when album cover isn't available ([0a0c78f](https://github.com/leinelissen/jellyfin-audio-player/commit/0a0c78f3d592e0d92a6bb3fd605810e0af1441bb))
* setup Fastlane for Google Play Store ([cc14373](https://github.com/leinelissen/jellyfin-audio-player/commit/cc14373575a844458737ac6f0a6e8d8ea783ce75))
## [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)

View File

@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
CFPropertyList (3.0.6)
rexml
activesupport (6.1.6)
concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -9,7 +9,7 @@ GEM
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
@@ -17,16 +17,16 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.660.0)
aws-sdk-core (3.167.0)
aws-partitions (1.743.0)
aws-sdk-core (3.171.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
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-sigv4 (~> 1.1)
aws-sdk-s3 (1.117.1)
aws-sdk-s3 (1.120.1)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
@@ -86,8 +86,8 @@ GEM
escape (0.0.4)
ethon (0.15.0)
ffi (>= 1.15.0)
excon (0.94.0)
faraday (1.10.2)
excon (0.99.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -116,7 +116,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.211.0)
fastlane (2.212.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -163,9 +163,9 @@ GEM
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.31.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-core (0.9.1)
google-apis-androidpublisher_v3 (0.38.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -174,10 +174,10 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.16.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-playcustomapp_v1 (0.12.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
@@ -185,7 +185,7 @@ GEM
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.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)
addressable (~> 2.8)
digest-crc (~> 0.4)
@@ -194,7 +194,7 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.3.0)
googleauth (1.5.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -207,11 +207,11 @@ GEM
httpclient (2.8.3)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
jmespath (1.6.1)
json (2.6.2)
jwt (2.5.0)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.0)
memoist (0.16.2)
mini_magick (4.11.0)
mini_magick (4.12.0)
mini_mime (1.1.2)
minitest (5.15.0)
molinillo (0.8.0)
@@ -223,7 +223,7 @@ GEM
netrc (0.11.0)
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
plist (3.7.0)
public_suffix (4.0.7)
rake (13.0.6)
representable (3.2.0)
@@ -242,7 +242,7 @@ GEM
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
simctl (1.6.10)
CFPropertyList
naturally
terminal-notifier (2.0.0)
@@ -262,7 +262,7 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)

View File

@@ -138,8 +138,8 @@ android {
applicationId "nl.moeilijkedingen.jellyfinaudioplayer"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 13
versionName "2.0.1"
versionCode 16
versionName "2.0.4"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
@@ -217,6 +217,14 @@ android {
keyAlias 'androiddebugkey'
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 {
debug {
@@ -225,7 +233,11 @@ android {
release {
// Caution! In production, you need to generate your own keystore file.
// 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
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}

View File

@@ -5,7 +5,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/react_native_config"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>

View File

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

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -1,4 +1,5 @@
package_name("nl.moeilijkedingen.jellyfinaudioplayer")
app_identifier("nl.moeilijkedingen.jellyfinaudioplayer")
apple_id("lei@moeilijkedingen.nl")
team_id("238P3C58WC")
team_id("238P3C58WC")
json_key_file("./fastlane/play-store-credentials.json")

View File

@@ -10,7 +10,7 @@ platform :ios do
get_provisioning_profile(
output_path: 'certificates/',
filename: "provisioning.mobileprovision",
fail_on_name_taken: true,
fail_on_name_taken: false,
)
update_code_signing_settings(
use_automatic_signing: true,
@@ -42,7 +42,16 @@ platform :ios do
workspace: "ios/Fintunes.xcworkspace",
export_method: "app-store",
)
upload_to_testflight
upload_to_testflight()
end
lane :build do
build_app(
scheme: "Fintunes",
output_directory: "build",
workspace: "ios/Fintunes.xcworkspace",
export_method: "app-store",
)
end
after_all do
@@ -101,8 +110,18 @@ platform :android do
gradle_file: "android/app/build.gradle"
)
gradle(
task: "assembleRelease",
task: "assemble",
build_type: "Release",
project_dir: "android"
)
end
lane :release do
gradle(
task: "bundle",
build_type: 'Release',
project_dir: "android"
)
upload_to_play_store
end
end

View File

@@ -31,6 +31,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
### ios build
```sh
[bundle exec] fastlane ios build
```
### ios screenshots
```sh
@@ -52,6 +60,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
Generate beta build
### android release
```sh
[bundle exec] fastlane android release
```
----
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.

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

View File

@@ -0,0 +1 @@
Streaming audio player for Jellyfin

View File

@@ -0,0 +1 @@
Fintunes

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

View File

@@ -606,7 +606,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = 238P3C58WC;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -643,7 +643,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = 238P3C58WC;
INFOPLIST_FILE = Fintunes/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -799,7 +799,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 55;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 238P3C58WC;
GCC_C_LANGUAGE_STANDARD = gnu11;
@@ -832,7 +832,7 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 49;
CURRENT_PROJECT_VERSION = 55;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 238P3C58WC;
GCC_C_LANGUAGE_STANDARD = gnu11;

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.0.1</string>
<string>2.0.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>49</string>
<string>55</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "fintunes",
"version": "2.0.1",
"version": "2.0.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "fintunes",
"version": "2.0.1",
"version": "2.0.4",
"dependencies": {
"@react-native-async-storage/async-storage": "^1.17.11",
"@react-native-community/blur": "^4.3.0",

View File

@@ -1,6 +1,6 @@
{
"name": "fintunes",
"version": "2.0.1",
"version": "2.0.4",
"main": "src/index.js",
"private": true,
"scripts": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 KiB

View File

@@ -2,7 +2,7 @@ import { BlurView, BlurViewProps } from '@react-native-community/blur';
import { THEME_COLOR } from 'CONSTANTS';
import React, { PropsWithChildren } 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;
@@ -108,14 +108,8 @@ export function ColoredBlurView(props: PropsWithChildren<BlurViewProps>) {
: scheme === 'dark' ? 'extraDark' : 'xlight'
} />
) : (
<BlurView
{...props}
blurType={scheme === 'dark' ? 'dark' : 'light'}
blurAmount={10}
style={[ props.style, {
backgroundColor: scheme === 'light' ? '#f6f6f6bb' : '#333333bb',
borderRadius: 8
} ]}
/>
<View {...props} style={[ props.style, {
backgroundColor: scheme === 'light' ? '#f6f6f6f6' : '#333333f6',
} ]} />
);
}

View File

@@ -1,5 +1,5 @@
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 useDefaultStyles from './Colors';
import styled from 'styled-components/native';
@@ -45,14 +45,18 @@ function CoverImage({
src,
}: Props) {
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 imageSize = Screen.width - margin;
const canvasSize = imageSize + blurRadius * 2;
return { imageSize, canvasSize };
}, [blurRadius, margin]);
const skiaImage = useMemo(() => (image || fallback), [image, fallback]);
return (
<Container size={imageSize} style={style}>
<BlurContainer size={canvasSize} offset={blurRadius}>
@@ -63,18 +67,16 @@ function CoverImage({
<Shadow dx={0} dy={8} blur={16} color="#0000000d" />
<Shadow dx={0} dy={16} blur={32} color="#0000000d" />
</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} />
<Blur blur={blurRadius / 2} />
</SkiaImage>
<Mask mask={<RoundedRect width={imageSize} height={imageSize} x={blurRadius} y={blurRadius} r={radius} />}>
{image ? (
<SkiaImage image={image} width={imageSize} height={imageSize}>
<Offset x={blurRadius} y={blurRadius} />
</SkiaImage>
) : null}
<SkiaImage image={skiaImage} width={imageSize} height={imageSize}>
<Offset x={blurRadius} y={blurRadius} />
</SkiaImage>
</Mask>
</>
) : null}

View File

@@ -35,7 +35,12 @@ function Input({ icon = null, style, testID, ...rest }: InputProps) {
<Gap size={8} />
</>
)}
<TextInput {...rest} style={{ margin: 0, padding: 0 }} ref={inputRef} testID={`${testID}-textinput`} />
<TextInput
{...rest}
style={[defaultStyles.text, { margin: 0, padding: 0 }]}
ref={inputRef}
testID={`${testID}-textinput`}
/>
</Container>
);
}

View File

@@ -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 { MusicNavigationProp } from '../types';
import { SafeAreaView, SectionList, View } from 'react-native';
@@ -20,22 +20,6 @@ import { ShadowWrapper } from 'components/Shadow';
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 }) {
return (
<SectionHeading label={section.label} key={section.label} />
@@ -105,42 +89,52 @@ const Albums: React.FC = () => {
const dispatch = useAppDispatch();
const navigation = useNavigation<MusicNavigationProp>();
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 } => {
// We must wait for the ref to become available before we can use the
// native item retriever in VirtualizedSectionList
if (!listRef.current) {
return { offset: 0, length: 0, index };
}
// Create an array that computes all the height data for the entire list in
// advance. We can then use this pre-computed data to respond to
// `getItemLayout` calls, without having to compute things in place (and
// fail horribly).
// 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
// @ts-ignore
const wrapperListRef = (listRef.current?._wrapperListRef) as VirtualizedSectionList;
const info: VirtualizedItemInfo = wrapperListRef._subExtractor(index);
const { index: itemIndex, header, key } = info;
const sectionIndex = parseInt(key.split(':')[0]);
// Loop through each individual section (i.e. alphabet letter) and add
// all items in that particular section.
sections.forEach((section) => {
// Each section starts with a header, so we'll need to add the item,
// as well as the offset.
layouts[index] = ({ length: HeadingHeight, offset, index });
index++;
offset += HeadingHeight;
// We can then determine the "length" (=height) of this item. Header items
// end up with an itemIndex of -1, thus are easy to identify.
const length = header ? 50 : (itemIndex % 2 === 0 ? AlbumHeight : 0);
// We'll also need to account for any unevenly-ended lists up until the
// current item.
const previousRows = data?.filter((row, i) => i < sectionIndex)
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
// Then, loop through all the rows (sets of two albums) and add
// items for those as well.
section.data.forEach(() => {
layouts[index] = ({ length: AlbumHeight, offset, index });
index++;
offset += AlbumHeight;
});
// We must also calcuate the offset, total distance from the top of the
// screen. First off, we'll account for each sectionIndex that is shown up
// until now. This only includes the heading for the current section if the
// item is not the section header
const headingOffset = HeadingHeight * (header ? sectionIndex : sectionIndex + 1);
const currentRows = itemIndex > 1 ? Math.ceil((itemIndex + 1) / 2) : 0;
const itemOffset = AlbumHeight * (previousRows + currentRows);
const offset = headingOffset + itemOffset;
return { index, length, offset };
}, [listRef]);
// The way SectionList works is that you get an item for a
// SectionHeader and a SectionFooter, no matter if you've specified
// whether you want them or not. Thus, we will need to add an empty
// footer as an item, so that we don't mismatch our indexes
layouts[index] = { length: 0, offset, index };
index++;
});
// Then, store and memoize the output
return layouts;
}, [sections]);
// Set callbacks
const retrieveData = useCallback(() => dispatch(fetchAllAlbums()), [dispatch]);
@@ -148,30 +142,19 @@ const Albums: React.FC = () => {
const selectLetter = useCallback((sectionIndex: number) => {
listRef.current?.scrollToLocation({ sectionIndex, itemIndex: 0, animated: false, });
}, [listRef]);
const generateItem = useCallback(({ item, index, section }: { item: EntityId, index: number, section: SectionedId }) => {
if (index % 2 === 1) {
return <View key={item} />;
}
const nextItem = section.data[index + 1];
const generateItem = useCallback(({ item }: { item: EntityId[] }) => {
return (
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item}>
<GeneratedAlbumItem
id={item}
imageUrl={getImage(item as string)}
name={albums[item]?.Name || ''}
artist={albums[item]?.AlbumArtist || ''}
onPress={selectAlbum}
/>
{albums[nextItem] &&
<View style={{ flexDirection: 'row', marginLeft: 10, marginRight: 10 }} key={item.join('-')}>
{item.map((id) => (
<GeneratedAlbumItem
id={nextItem}
imageUrl={getImage(nextItem as string)}
name={albums[nextItem]?.Name || ''}
artist={albums[nextItem]?.AlbumArtist || ''}
key={id}
id={id}
imageUrl={getImage(id as string)}
name={albums[id]?.Name || ''}
artist={albums[id]?.AlbumArtist || ''}
onPress={selectAlbum}
/>
}
))}
</View>
);
}, [albums, getImage, selectAlbum]);
@@ -191,9 +174,9 @@ const Albums: React.FC = () => {
sections={sections}
refreshing={isLoading}
onRefresh={retrieveData}
getItemLayout={getItemLayout}
getItemLayout={(_, i) => itemLayouts[i] ?? { length: 0, offset: 0, index: i }}
ref={listRef}
keyExtractor={(item) => item as string}
keyExtractor={(item) => item.join('-')}
renderSectionHeader={generateSection}
renderItem={generateItem}
/>

View File

@@ -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;

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

View File

@@ -28,8 +28,7 @@ import ticksToDuration from 'utility/ticksToDuration';
const styles = StyleSheet.create({
index: {
width: 16,
marginRight: 8
marginRight: 12
},
activeText: {
color: THEME_COLOR,

View File

@@ -50,7 +50,7 @@ export const selectAlbumsByArtist = createSelector(
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
@@ -58,13 +58,26 @@ export type SectionedId = SectionListData<EntityId>;
function splitAlbumsByAlphabet(state: AppState['music']['albums']): SectionedId[] {
const { entities: albums } = 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) => {
// Retrieve the album letter and corresponding letter index
const album = albums[id];
const letter = album?.AlbumArtist?.toUpperCase().charAt(0);
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;