Compare commits
11 Commits
v2.0.3
...
feat/andro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4508f6cc1c | ||
|
|
56647cd7ab | ||
|
|
1648389ccc | ||
|
|
a532154ce0 | ||
|
|
74d82eb77a | ||
|
|
a8c0003fc1 | ||
|
|
ba805e061e | ||
|
|
cc14373575 | ||
|
|
943815e4a6 | ||
|
|
2f45f868c8 | ||
|
|
0a0c78f3d5 |
3
.gitignore
vendored
@@ -72,4 +72,5 @@ certificates/
|
||||
sentry.properties
|
||||
|
||||
screenshots
|
||||
fastlane/Preview.html
|
||||
fastlane/Preview.html
|
||||
fastlane/play-store-credentials.json
|
||||
50
Gemfile.lock
@@ -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)
|
||||
|
||||
@@ -141,6 +141,7 @@ android {
|
||||
versionCode 15
|
||||
versionName "2.0.3"
|
||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
// We configure the CMake build only if you decide to opt-in for the New Architecture.
|
||||
@@ -217,6 +218,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 +234,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"
|
||||
}
|
||||
@@ -269,6 +282,21 @@ dependencies {
|
||||
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) {
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
|
||||
|
||||
105
android/app/src/androidTest/java/ScreenshotTest.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package nl.moeilijkedingen.jellyfinaudioplayer;
|
||||
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import nl.moeilijkedingen.jellyfinaudioplayer.R;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import java.util.Arrays;
|
||||
|
||||
import tools.fastlane.screengrab.Screengrab;
|
||||
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
|
||||
import tools.fastlane.screengrab.cleanstatusbar.BluetoothState;
|
||||
import tools.fastlane.screengrab.cleanstatusbar.CleanStatusBar;
|
||||
import tools.fastlane.screengrab.cleanstatusbar.MobileDataType;
|
||||
import tools.fastlane.screengrab.locale.LocaleTestRule;
|
||||
|
||||
import androidx.test.espresso.NoMatchingViewException;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
|
||||
import static org.hamcrest.core.AllOf.allOf;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class ScreenshotTest {
|
||||
@ClassRule
|
||||
public static final LocaleTestRule localeTestRule = new LocaleTestRule();
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class);
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAll() {
|
||||
Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
|
||||
CleanStatusBar.enableWithDefaults();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterAll() {
|
||||
CleanStatusBar.disable();
|
||||
}
|
||||
|
||||
/*
|
||||
Custom wait function. In order to make sure each button press yields a
|
||||
desirable screen, we use the wait function to delay further actions until
|
||||
the current one has achieved its purpose.
|
||||
|
||||
`duration` indicates the amount of milli-seconds to wait. The value of
|
||||
`duration` is acquired by emperical trial-and-error.
|
||||
*/
|
||||
public void wait(int duration) {
|
||||
try {
|
||||
Thread.sleep(duration);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void inputText(Integer id, String text) {
|
||||
try {
|
||||
onView(allOf(withId(id))).perform(typeText(text));
|
||||
} catch (NoMatchingViewException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTakeScreenshot() {
|
||||
System.out.println("AVAILABLE IDS:" + Arrays.toString(R.id.class.getFields()));
|
||||
// wait(10000);
|
||||
|
||||
// Screengrab.screenshot("04RecentAlbums");
|
||||
// onView(allOf(withId(R.id.all_albums))).perform(click());
|
||||
// wait(5000);
|
||||
// Screengrab.screenshot("05AlbumsScreen");
|
||||
|
||||
// onView(allOf(withId(R.id.search_tab))).perform(click());
|
||||
// wait(5000);
|
||||
// onView(allOf(withId(R.id.search_input_container))).perform(click());
|
||||
// wait(5000);
|
||||
// onView(allOf(withId(R.id.search_input_textinput))).perform(typeText("bicep"));
|
||||
// wait(5000);
|
||||
|
||||
// onView(allOf(withId(R.id.search_result_a644f8d23821601d2feb86ddae5e64f4))).perform(click());
|
||||
// wait(5000);
|
||||
// Screengrab.screenshot("02AlbumScreen");
|
||||
|
||||
// onView(allOf(withId(R.id.play_album))).perform(click());
|
||||
// wait(5000);
|
||||
// onView(allOf(withId(R.id.open_player_modal))).perform(click());
|
||||
// wait(5000);
|
||||
// Screengrab.screenshot("01PlayModal");
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,18 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<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
|
||||
android:networkSecurityConfig="@xml/react_native_config"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# 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.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
|
||||
@@ -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")
|
||||
@@ -44,6 +44,15 @@ platform :ios do
|
||||
)
|
||||
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
|
||||
build_number = get_build_number(
|
||||
@@ -101,8 +110,36 @@ 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
|
||||
|
||||
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
|
||||
|
||||
@@ -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,22 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
||||
|
||||
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.
|
||||
|
||||
1
fastlane/metadata/android/en-GB/full_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Fintunes is a streaming audio player for the Jellyfin media system. It features a gorgeous interface that allows you to play your favourite music with ease. You can search your entire library for any track, or just take it easy with a playlist that you've created earlier in Jellyfin. All tracks are streamed directly at the highest quality from your Jellyfin library. Streaming not always an option? Any track in your Jellyfin library can be downloaded and played offline.
|
||||
1
fastlane/metadata/android/en-GB/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
Streaming audio player for Jellyfin
|
||||
1
fastlane/metadata/android/en-GB/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
Fintunes
|
||||
0
fastlane/metadata/android/en-GB/video.txt
Normal file
|
After Width: | Height: | Size: 746 KiB |
|
After Width: | Height: | Size: 730 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 366 KiB |
BIN
src/assets/images/empty-album-dark.png
Normal file
|
After Width: | Height: | Size: 1005 KiB |
BIN
src/assets/images/empty-album-light.png
Normal file
|
After Width: | Height: | Size: 926 KiB |
@@ -2,7 +2,7 @@ import { BlurView, BlurViewProps } from '@react-native-community/blur';
|
||||
import { THEME_COLOR } from 'CONSTANTS';
|
||||
import 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',
|
||||
} ]} />
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import styled from 'styled-components/native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import { Dimensions } from 'react-native';
|
||||
|
||||
const Screen = Dimensions.get('screen');
|
||||
export const AlbumWidth = Screen.width / 2 - 24;
|
||||
export const AlbumHeight = AlbumWidth + 40;
|
||||
export const CoverSize = AlbumWidth - 16;
|
||||
|
||||
export const AlbumItem = styled.View`
|
||||
width: ${AlbumWidth}px;
|
||||
height: ${AlbumHeight}px;
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const AlbumImage = styled(FastImage)`
|
||||
border-radius: 10px;
|
||||
width: ${CoverSize}px;
|
||||
height: ${CoverSize}px;
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
export default AlbumImage;
|
||||
39
src/screens/Music/stacks/components/AlbumImage.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components/native';
|
||||
import FastImage, { FastImageProps } from 'react-native-fast-image';
|
||||
import { Dimensions, useColorScheme } from 'react-native';
|
||||
|
||||
const Screen = Dimensions.get('screen');
|
||||
export const AlbumWidth = Screen.width / 2 - 24;
|
||||
export const AlbumHeight = AlbumWidth + 40;
|
||||
export const CoverSize = AlbumWidth - 16;
|
||||
|
||||
export const AlbumItem = styled.View`
|
||||
width: ${AlbumWidth}px;
|
||||
height: ${AlbumHeight}px;
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
const Container = styled(FastImage)`
|
||||
border-radius: 10px;
|
||||
width: ${CoverSize}px;
|
||||
height: ${CoverSize}px;
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
function AlbumImage(props: FastImageProps) {
|
||||
const [hasError, setError] = useState(false);
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
if (!props.source || hasError) {
|
||||
return (
|
||||
<Container source={colorScheme === 'light' ? require('assets/images/empty-album-light.png') : require('assets/images/empty-album-dark.png')} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container {...props} onError={() => setError(true)} />
|
||||
);
|
||||
}
|
||||
|
||||
export default AlbumImage;
|
||||