Compare commits

...

24 Commits

Author SHA1 Message Date
Lei Nelissen
4508f6cc1c draft: android screenshots 2023-04-11 18:34:48 +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
Lei Nelissen
6d8535e24b chore: release v2.0.1 2022-11-28 22:59:29 +01:00
Lei Nelissen
d4570b60ae fix: screenshotting logic 2022-11-28 22:56:22 +01:00
Lei Nelissen
9c8e474d51 feat: Save App metadata in the repo 2022-11-28 22:55:36 +01:00
Lei Nelissen
9a1defbeef fix: switch album id to demo instance 2022-11-27 21:19:31 +01:00
Lei Nelissen
87f992d912 fix: use entire input box as touch area for focus 2022-11-27 21:13:09 +01:00
Lei Nelissen
845b379e09 fix: android and ios builds 2022-11-27 21:07:32 +01:00
97 changed files with 714 additions and 237 deletions

3
.gitignore vendored
View File

@@ -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
View 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)

View File

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

View File

@@ -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 11 versionCode 15
versionName "1.2.7" 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

View 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");
}
}

View File

@@ -3,9 +3,21 @@
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:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/react_native_config"
tools:targetApi="28" tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning"> tools:ignore="GoogleAppIndexingWarning">
<activity <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:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false" android:allowBackup="false"
android:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/react_native_config"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".MainActivity" 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

@@ -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
@@ -25,7 +25,7 @@ android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# Version of flipper SDK to use with React Native # Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0 FLIPPER_VERSION=0.164.0
# Use this property to specify which architecture you want to build. # Use this property to specify which architecture you want to build.
# You can also override it from the CLI using # You can also override it from the CLI using

View File

@@ -32,6 +32,7 @@ module.exports = {
[ [
'module:react-native-dotenv' 'module:react-native-dotenv'
], ],
'react-native-reanimated/plugin' 'react-native-reanimated/plugin',
'@babel/plugin-proposal-numeric-separator'
] ]
}; };

View File

@@ -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")

View File

@@ -1,3 +1,4 @@
# The Deliverfile allows you to store various App Store Connect metadata # The Deliverfile allows you to store various App Store Connect metadata
# For more information, check out the docs # For more information, check out the docs
# https://docs.fastlane.tools/actions/deliver/ # https://docs.fastlane.tools/actions/deliver/
overwrite_screenshots(true)

View File

@@ -10,7 +10,7 @@ platform :ios do
get_provisioning_profile( get_provisioning_profile(
output_path: 'certificates/', output_path: 'certificates/',
filename: "provisioning.mobileprovision", filename: "provisioning.mobileprovision",
fail_on_name_taken: true, fail_on_name_taken: false,
) )
update_code_signing_settings( update_code_signing_settings(
use_automatic_signing: true, use_automatic_signing: true,
@@ -42,7 +42,16 @@ 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()
end
lane :build do
build_app(
scheme: "Fintunes",
output_directory: "build",
workspace: "ios/Fintunes.xcworkspace",
export_method: "app-store",
)
end end
after_all do after_all do
@@ -101,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

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

View File

@@ -4,7 +4,6 @@
devices([ devices([
"iPhone 14 Plus", "iPhone 14 Plus",
"iPhone 14 Pro Max", "iPhone 14 Pro Max",
"iPhone 14 Pro",
"iPhone 8 Plus", "iPhone 8 Plus",
]) ])

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

@@ -1 +1 @@
2022 Lei Nelissen

View File

@@ -1 +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

@@ -1 +1 @@
Fintunes Fintunes

View File

@@ -1 +1 @@
https://github.com/leinelissen/jellyfin-audio-player/blob/master/docs/privacy-policy.md

View File

@@ -1 +1 @@
Audio player for Jellyfin

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes es un reproductor de transmisión de audio para el sistema multimedia Jellyfin. Cuenta con una hermosa interfaz que te permite reproducir tu música favorita con facilidad. Puede buscar cualquier pista en toda su biblioteca, o simplemente tomarlo con calma con una lista de reproducción que haya creado anteriormente en Jellyfin. Todas las pistas se transmiten directamente con la más alta calidad desde su biblioteca Jellyfin. ¿Transmitir no siempre es una opción? Cualquier pista en su biblioteca Jellyfin se puede descargar y reproducir sin conexión.

View File

@@ -0,0 +1 @@
gelatina, audio, reproductor, transmisión, descargas, música

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player/blob/master/docs/privacy-policy.md

View File

@@ -0,0 +1 @@
Un reproductor de audio en streaming para Jellyfin, con soporte para búsqueda y descargas.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Audio player for Jellyfin

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes est un lecteur audio en streaming pour le système multimédia Jellyfin. Il dispose d'une interface magnifique qui vous permet de jouer facilement votre musique préférée. Vous pouvez rechercher n'importe quelle piste dans toute votre bibliothèque ou simplement vous détendre avec une liste de lecture que vous avez créée précédemment dans Jellyfin. Toutes les pistes sont diffusées directement à la plus haute qualité depuis votre bibliothèque Jellyfin. Le streaming n'est pas toujours une option ? N'importe quelle piste de votre bibliothèque Jellyfin peut être téléchargée et lue hors ligne.

View File

@@ -0,0 +1 @@
jellyfin, audio, joueur, diffusion, téléchargements, musique

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player/blob/master/docs/privacy-policy.md

View File

@@ -0,0 +1 @@
Un lecteur audio en streaming pour Jellyfin, avec prise en charge de la recherche et des téléchargements.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Audio player for Jellyfin

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunesは、Jellyfinメディアシステム用のストリーミングオーディオプレーヤーです。 それはあなたが簡単にあなたの好きな音楽を演奏することを可能にするゴージャスなインターフェースを特徴とします。 ライブラリ全体で任意のトラックを検索することも、Jellyfinで以前に作成したプレイリストを使用して簡単に検索することもできます。 すべてのトラックは、Jellyfinライブラリから最高品質で直接ストリーミングされます。 ストリーミングは必ずしもオプションではありませんか? Jellyfinライブラリ内のすべてのトラックをダウンロードして、オフラインで再生できます。

View File

@@ -0,0 +1 @@
ジェリーフィン、オーディオ、プレーヤー、ストリーミング、ダウンロード、音楽

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player/blob/master/docs/privacy-policy.md

View File

@@ -0,0 +1 @@
Jellyfin用のストリーミングオーディオプレーヤー。検索とダウンロードをサポートしています。

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Audio player for Jellyfin

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes is een streaming audiospeler voor het Jellyfin mediasysteem. Het heeft een prachtige interface waarmee je gemakkelijk je favoriete muziek kunt afspelen. Je kunt in je hele bibliotheek zoeken naar elk nummer, of het rustig aan doen met een afspeellijst die je eerder in Jellyfin hebt gemaakt. Alle nummers worden direct in de hoogste kwaliteit gestreamd vanuit je Jellyfin-bibliotheek. Streamen niet altijd een optie? Elk nummer in je Jellyfin-bibliotheek kan worden gedownload en offline worden afgespeeld.

View File

@@ -0,0 +1 @@
jellyfin, audio, speler, streaming, downloads, muziek

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player/blob/master/docs/privacy-policy.md

View File

@@ -0,0 +1 @@
Een streaming audiospeler voor Jellyfin, met ondersteuning voor zoeken en downloaden.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Audiospeler voor Jellyfin

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes 是 Jellyfin 媒体系统的流式音频播放器。 它具有华丽的界面,可让您轻松播放自己喜欢的音乐。 您可以在整个音乐库中搜索任何曲目,或者只是使用您之前在 Jellyfin 中创建的播放列表轻松一点。 所有曲目都直接以最高质量从您的 Jellyfin 库中流式传输。 流媒体并不总是一种选择? Jellyfin 库中的任何曲目都可以下载和离线播放。

View File

@@ -0,0 +1 @@
jellyfin, 音频, 播放器, 流媒体, 下载, 音乐

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Fintunes

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player/blob/master/docs/privacy-policy.md

View File

@@ -0,0 +1 @@
Jellyfin 的流媒体音频播放器,支持搜索和下载。

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Audio player for Jellyfin

View File

@@ -0,0 +1 @@
https://github.com/leinelissen/jellyfin-audio-player

View File

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

View File

@@ -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.0</string> <string>2.0.3</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>6</string> <string>53</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>

View File

@@ -33,12 +33,12 @@ class FintunesUITests: XCTestCase {
app.otherElements["all-albums"].tap(); app.otherElements["all-albums"].tap();
snapshot("05AlbumsScreen"); snapshot("05AlbumsScreen");
app.buttons["search-tab"].tap(); app.buttons["search-tab"].tap();
app.textFields["search-input"].tap(); app.otherElements["search-input-container"].tap();
app.textFields["search-input"].typeText("bicep"); app.textFields["search-input-textinput"].typeText("bicep")
snapshot("03SearchScreen"); snapshot("03SearchScreen");
if app.otherElements["search-result-157a37e93a7aec945f8ea3107abb458a"].waitForExistence(timeout: 5) { if app.otherElements["search-result-a644f8d23821601d2feb86ddae5e64f4"].waitForExistence(timeout: 5) {
app.otherElements["search-result-157a37e93a7aec945f8ea3107abb458a"].tap(); app.otherElements["search-result-a644f8d23821601d2feb86ddae5e64f4"].tap();
app.otherElements["search-result-157a37e93a7aec945f8ea3107abb458a"].tap(); app.otherElements["search-result-a644f8d23821601d2feb86ddae5e64f4"].tap();
snapshot("02AlbumScreen"); snapshot("02AlbumScreen");
} }
if app.otherElements["play-album"].waitForExistence(timeout: 5) { if app.otherElements["play-album"].waitForExistence(timeout: 5) {

View File

@@ -21,7 +21,7 @@ target 'Fintunes' do
# #
# Note that if you have use_frameworks! enabled, Flipper will not work and # Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line. # you should disable the next line.
:flipper_configuration => FlipperConfiguration.enabled, :flipper_configuration => FlipperConfiguration.enabled(["Debug"], {'Flipper' => '0.164.0'}),
# An absolute path to your application root. # An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.." :app_path => "#{Pod::Config.instance.installation_root}/.."
) )

View File

@@ -10,9 +10,8 @@ PODS:
- React-Core (= 0.70.5) - React-Core (= 0.70.5)
- React-jsi (= 0.70.5) - React-jsi (= 0.70.5)
- ReactCommon/turbomodule/core (= 0.70.5) - ReactCommon/turbomodule/core (= 0.70.5)
- Flipper (0.125.0): - Flipper (0.164.0):
- Flipper-Folly (~> 2.6) - Flipper-Folly (~> 2.6)
- Flipper-RSocket (~> 1.4)
- Flipper-Boost-iOSX (1.76.0.1.11) - Flipper-Boost-iOSX (1.76.0.1.11)
- Flipper-DoubleConversion (3.2.0.1) - Flipper-DoubleConversion (3.2.0.1)
- Flipper-Fmt (7.1.7) - Flipper-Fmt (7.1.7)
@@ -27,48 +26,48 @@ PODS:
- Flipper-PeerTalk (0.0.4) - Flipper-PeerTalk (0.0.4)
- Flipper-RSocket (1.4.3): - Flipper-RSocket (1.4.3):
- Flipper-Folly (~> 2.6) - Flipper-Folly (~> 2.6)
- FlipperKit (0.125.0): - FlipperKit (0.164.0):
- FlipperKit/Core (= 0.125.0) - FlipperKit/Core (= 0.164.0)
- FlipperKit/Core (0.125.0): - FlipperKit/Core (0.164.0):
- Flipper (~> 0.125.0) - Flipper (~> 0.164.0)
- FlipperKit/CppBridge - FlipperKit/CppBridge
- FlipperKit/FBCxxFollyDynamicConvert - FlipperKit/FBCxxFollyDynamicConvert
- FlipperKit/FBDefines - FlipperKit/FBDefines
- FlipperKit/FKPortForwarding - FlipperKit/FKPortForwarding
- SocketRocket (~> 0.6.0) - SocketRocket (~> 0.6.0)
- FlipperKit/CppBridge (0.125.0): - FlipperKit/CppBridge (0.164.0):
- Flipper (~> 0.125.0) - Flipper (~> 0.164.0)
- FlipperKit/FBCxxFollyDynamicConvert (0.125.0): - FlipperKit/FBCxxFollyDynamicConvert (0.164.0):
- Flipper-Folly (~> 2.6) - Flipper-Folly (~> 2.6)
- FlipperKit/FBDefines (0.125.0) - FlipperKit/FBDefines (0.164.0)
- FlipperKit/FKPortForwarding (0.125.0): - FlipperKit/FKPortForwarding (0.164.0):
- CocoaAsyncSocket (~> 7.6) - CocoaAsyncSocket (~> 7.6)
- Flipper-PeerTalk (~> 0.0.4) - Flipper-PeerTalk (~> 0.0.4)
- FlipperKit/FlipperKitHighlightOverlay (0.125.0) - FlipperKit/FlipperKitHighlightOverlay (0.164.0)
- FlipperKit/FlipperKitLayoutHelpers (0.125.0): - FlipperKit/FlipperKitLayoutHelpers (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutTextSearchable - FlipperKit/FlipperKitLayoutTextSearchable
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.125.0): - FlipperKit/FlipperKitLayoutIOSDescriptors (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutHelpers - FlipperKit/FlipperKitLayoutHelpers
- YogaKit (~> 1.18) - YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutPlugin (0.125.0): - FlipperKit/FlipperKitLayoutPlugin (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitHighlightOverlay - FlipperKit/FlipperKitHighlightOverlay
- FlipperKit/FlipperKitLayoutHelpers - FlipperKit/FlipperKitLayoutHelpers
- FlipperKit/FlipperKitLayoutIOSDescriptors - FlipperKit/FlipperKitLayoutIOSDescriptors
- FlipperKit/FlipperKitLayoutTextSearchable - FlipperKit/FlipperKitLayoutTextSearchable
- YogaKit (~> 1.18) - YogaKit (~> 1.18)
- FlipperKit/FlipperKitLayoutTextSearchable (0.125.0) - FlipperKit/FlipperKitLayoutTextSearchable (0.164.0)
- FlipperKit/FlipperKitNetworkPlugin (0.125.0): - FlipperKit/FlipperKitNetworkPlugin (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitReactPlugin (0.125.0): - FlipperKit/FlipperKitReactPlugin (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitUserDefaultsPlugin (0.125.0): - FlipperKit/FlipperKitUserDefaultsPlugin (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.125.0): - FlipperKit/SKIOSNetworkPlugin (0.164.0):
- FlipperKit/Core - FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin - FlipperKit/FlipperKitNetworkPlugin
- fmt (6.2.1) - fmt (6.2.1)
@@ -315,7 +314,7 @@ PODS:
- glog - glog
- react-native-blur (4.3.0): - react-native-blur (4.3.0):
- React-Core - React-Core
- react-native-flipper (0.174.0): - react-native-flipper (0.164.0):
- React-Core - React-Core
- react-native-netinfo (9.3.6): - react-native-netinfo (9.3.6):
- React-Core - React-Core
@@ -492,7 +491,7 @@ DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
- Flipper (= 0.125.0) - Flipper (= 0.164.0)
- Flipper-Boost-iOSX (= 1.76.0.1.11) - Flipper-Boost-iOSX (= 1.76.0.1.11)
- Flipper-DoubleConversion (= 3.2.0.1) - Flipper-DoubleConversion (= 3.2.0.1)
- Flipper-Fmt (= 7.1.7) - Flipper-Fmt (= 7.1.7)
@@ -500,19 +499,19 @@ DEPENDENCIES:
- Flipper-Glog (= 0.5.0.5) - Flipper-Glog (= 0.5.0.5)
- Flipper-PeerTalk (= 0.0.4) - Flipper-PeerTalk (= 0.0.4)
- Flipper-RSocket (= 1.4.3) - Flipper-RSocket (= 1.4.3)
- FlipperKit (= 0.125.0) - FlipperKit (= 0.164.0)
- FlipperKit/Core (= 0.125.0) - FlipperKit/Core (= 0.164.0)
- FlipperKit/CppBridge (= 0.125.0) - FlipperKit/CppBridge (= 0.164.0)
- FlipperKit/FBCxxFollyDynamicConvert (= 0.125.0) - FlipperKit/FBCxxFollyDynamicConvert (= 0.164.0)
- FlipperKit/FBDefines (= 0.125.0) - FlipperKit/FBDefines (= 0.164.0)
- FlipperKit/FKPortForwarding (= 0.125.0) - FlipperKit/FKPortForwarding (= 0.164.0)
- FlipperKit/FlipperKitHighlightOverlay (= 0.125.0) - FlipperKit/FlipperKitHighlightOverlay (= 0.164.0)
- FlipperKit/FlipperKitLayoutPlugin (= 0.125.0) - FlipperKit/FlipperKitLayoutPlugin (= 0.164.0)
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.125.0) - FlipperKit/FlipperKitLayoutTextSearchable (= 0.164.0)
- FlipperKit/FlipperKitNetworkPlugin (= 0.125.0) - FlipperKit/FlipperKitNetworkPlugin (= 0.164.0)
- FlipperKit/FlipperKitReactPlugin (= 0.125.0) - FlipperKit/FlipperKitReactPlugin (= 0.164.0)
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.125.0) - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.164.0)
- FlipperKit/SKIOSNetworkPlugin (= 0.125.0) - FlipperKit/SKIOSNetworkPlugin (= 0.164.0)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`)
- libevent (~> 2.1.12) - libevent (~> 2.1.12)
@@ -698,7 +697,7 @@ SPEC CHECKSUMS:
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: affa4ba1bfdaac110a789192f4d452b053a86624 FBLazyVector: affa4ba1bfdaac110a789192f4d452b053a86624
FBReactNativeSpec: fe8b5f1429cfe83a8d72dc8ed61dc7704cac8745 FBReactNativeSpec: fe8b5f1429cfe83a8d72dc8ed61dc7704cac8745
Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 Flipper: d08578a2cc23c60c27086b07930efaeb39101342
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
@@ -706,7 +705,7 @@ SPEC CHECKSUMS:
Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446 Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541 Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 FlipperKit: ddf459d2625ca33f115492de5ba6d970e2576311
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 7fe5fc6ef707b7fdcb161b63898ec500e285653d hermes-engine: 7fe5fc6ef707b7fdcb161b63898ec500e285653d
@@ -730,7 +729,7 @@ SPEC CHECKSUMS:
React-jsinspector: badd81696361249893a80477983e697aab3c1a34 React-jsinspector: badd81696361249893a80477983e697aab3c1a34
React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c React-logger: fdda34dd285bdb0232e059b19d9606fa0ec3bb9c
react-native-blur: 50c9feabacbc5f49b61337ebc32192c6be7ec3c3 react-native-blur: 50c9feabacbc5f49b61337ebc32192c6be7ec3c3
react-native-flipper: b269b4d4e1ec04f7f443f5edf15100a13e760bf0 react-native-flipper: c33a4995958ef12a2b2f8290d63bed7adeed7634
react-native-netinfo: f80db8cac2151405633324cb645c60af098ee461 react-native-netinfo: f80db8cac2151405633324cb645c60af098ee461
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
react-native-skia: 7f9a3bd36c4247005e87005d912dcf6db76a6289 react-native-skia: 7f9a3bd36c4247005e87005d912dcf6db76a6289
@@ -765,6 +764,6 @@ SPEC CHECKSUMS:
Yoga: eca980a5771bf114c41a754098cd85e6e0d90ed7 Yoga: eca980a5771bf114c41a754098cd85e6e0d90ed7
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 9735b44bd285aab03d16f4b0a062c94bf98c573c PODFILE CHECKSUM: 94434618afff1be257dd0576e9a75bcaa7b48664
COCOAPODS: 1.11.3 COCOAPODS: 1.11.3

45
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "fintunes", "name": "fintunes",
"version": "2.0.0", "version": "2.0.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "fintunes", "name": "fintunes",
"version": "2.0.0", "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",
@@ -31,7 +31,7 @@
"react-native-collapsible": "^1.6.0", "react-native-collapsible": "^1.6.0",
"react-native-dotenv": "^3.4.2", "react-native-dotenv": "^3.4.2",
"react-native-fast-image": "^8.6.3", "react-native-fast-image": "^8.6.3",
"react-native-flipper": "^0.174.0", "react-native-flipper": "^0.164.0",
"react-native-fs": "^2.20.0", "react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.8.0", "react-native-gesture-handler": "^2.8.0",
"react-native-localize": "^2.2.4", "react-native-localize": "^2.2.4",
@@ -52,6 +52,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.2", "@babel/core": "^7.20.2",
"@babel/plugin-proposal-numeric-separator": "^7.18.6",
"@babel/runtime": "^7.20.1", "@babel/runtime": "^7.20.1",
"@react-native-community/eslint-config": "^3.2.0", "@react-native-community/eslint-config": "^3.2.0",
"@sentry/cli": "^2.8.1", "@sentry/cli": "^2.8.1",
@@ -653,6 +654,22 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/plugin-proposal-numeric-separator": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
"integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
"dev": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-object-rest-spread": { "node_modules/@babel/plugin-proposal-object-rest-spread": {
"version": "7.18.9", "version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz",
@@ -13731,9 +13748,9 @@
} }
}, },
"node_modules/react-native-flipper": { "node_modules/react-native-flipper": {
"version": "0.174.0", "version": "0.164.0",
"resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.174.0.tgz", "resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.164.0.tgz",
"integrity": "sha512-9WAUxqHLU57dYxD/Z5EkcU/MdP1KFWCxdZfeHJ6Ogq/GbJVawzibjVfGPVwc2GoLIMvE6rZ0Fvvw4AUIdykc/g==", "integrity": "sha512-iJhIe3rqx6okuzBp4AJsTa2b8VRAOGzoLRFx/4HGbaGvu8AurZjz8TTQkhJsRma8dsHN2b6KKZPvGGW3wdWzvA==",
"peerDependencies": { "peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-native": ">0.62.0" "react-native": ">0.62.0"
@@ -16719,6 +16736,16 @@
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
} }
}, },
"@babel/plugin-proposal-numeric-separator": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz",
"integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
}
},
"@babel/plugin-proposal-object-rest-spread": { "@babel/plugin-proposal-object-rest-spread": {
"version": "7.18.9", "version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz",
@@ -26867,9 +26894,9 @@
"integrity": "sha512-Sdw4ESidXCXOmQ9EcYguNY2swyoWmx53kym2zRsvi+VeFCHEdkO+WG1DK+6W81juot40bbfLNhkc63QnWtesNg==" "integrity": "sha512-Sdw4ESidXCXOmQ9EcYguNY2swyoWmx53kym2zRsvi+VeFCHEdkO+WG1DK+6W81juot40bbfLNhkc63QnWtesNg=="
}, },
"react-native-flipper": { "react-native-flipper": {
"version": "0.174.0", "version": "0.164.0",
"resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.174.0.tgz", "resolved": "https://registry.npmjs.org/react-native-flipper/-/react-native-flipper-0.164.0.tgz",
"integrity": "sha512-9WAUxqHLU57dYxD/Z5EkcU/MdP1KFWCxdZfeHJ6Ogq/GbJVawzibjVfGPVwc2GoLIMvE6rZ0Fvvw4AUIdykc/g==" "integrity": "sha512-iJhIe3rqx6okuzBp4AJsTa2b8VRAOGzoLRFx/4HGbaGvu8AurZjz8TTQkhJsRma8dsHN2b6KKZPvGGW3wdWzvA=="
}, },
"react-native-fs": { "react-native-fs": {
"version": "2.20.0", "version": "2.20.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "fintunes", "name": "fintunes",
"version": "2.0.0", "version": "2.0.3",
"main": "src/index.js", "main": "src/index.js",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -9,7 +9,7 @@
"start": "react-native start", "start": "react-native start",
"test": "jest", "test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx && tsc --noEmit", "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'" "build:ios": "react-native bundle --entry-file='index.js' --bundle-output='./ios/main.jsbundle' --dev=false --platform='ios'"
}, },
"dependencies": { "dependencies": {
"@react-native-async-storage/async-storage": "^1.17.11", "@react-native-async-storage/async-storage": "^1.17.11",
@@ -35,7 +35,7 @@
"react-native-collapsible": "^1.6.0", "react-native-collapsible": "^1.6.0",
"react-native-dotenv": "^3.4.2", "react-native-dotenv": "^3.4.2",
"react-native-fast-image": "^8.6.3", "react-native-fast-image": "^8.6.3",
"react-native-flipper": "^0.174.0", "react-native-flipper": "^0.164.0",
"react-native-fs": "^2.20.0", "react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.8.0", "react-native-gesture-handler": "^2.8.0",
"react-native-localize": "^2.2.4", "react-native-localize": "^2.2.4",
@@ -56,6 +56,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.2", "@babel/core": "^7.20.2",
"@babel/plugin-proposal-numeric-separator": "^7.18.6",
"@babel/runtime": "^7.20.1", "@babel/runtime": "^7.20.1",
"@react-native-community/eslint-config": "^3.2.0", "@react-native-community/eslint-config": "^3.2.0",
"@sentry/cli": "^2.8.1", "@sentry/cli": "^2.8.1",

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 { 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
} ]}
/>
); );
} }

View File

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

View File

@@ -1,14 +1,14 @@
import React from 'react'; import React, { useCallback, useRef } from 'react';
import { Platform, TextInputProps } from 'react-native'; import { Platform, TextInput, TextInputProps } from 'react-native';
import styled, { css } from 'styled-components/native'; import styled, { css } from 'styled-components/native';
import useDefaultStyles from './Colors'; import useDefaultStyles from './Colors';
import { Gap } from './Utility'; import { Gap } from './Utility';
export interface InputProps extends TextInputProps { export interface InputProps extends TextInputProps {
icon?: React.ReactNode icon?: React.ReactNode;
} }
const Container = styled.View` const Container = styled.Pressable`
margin: 6px 0; margin: 6px 0;
border-radius: 8px; border-radius: 8px;
display: flex; display: flex;
@@ -21,23 +21,26 @@ const Container = styled.View`
})} })}
`; `;
const InputWrapper = styled.TextInput` function Input({ icon = null, style, testID, ...rest }: InputProps) {
margin: 0;
padding: 0;
`;
function Input({ icon = null, style, ...rest }: InputProps) {
const defaultStyles = useDefaultStyles(); const defaultStyles = useDefaultStyles();
const inputRef = useRef<TextInput | null>(null);
const handlePress = useCallback(() => inputRef.current?.focus(), []);
return ( return (
<Container style={[defaultStyles.input, style]}> <Container style={[defaultStyles.input, style]} onPress={handlePress} testID={`${testID}-container`} accessible={false}>
{icon && ( {icon && (
<> <>
{icon} {icon}
<Gap size={8} /> <Gap size={8} />
</> </>
)} )}
<InputWrapper {...rest} /> <TextInput
{...rest}
style={[defaultStyles.text, { margin: 0, padding: 0 }]}
ref={inputRef}
testID={`${testID}-textinput`}
/>
</Container> </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 { 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}
/> />

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({ const styles = StyleSheet.create({
index: { index: {
width: 16, marginRight: 12
marginRight: 8
}, },
activeText: { activeText: {
color: THEME_COLOR, color: THEME_COLOR,

View File

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