diff --git a/android/app/build.gradle b/android/app/build.gradle
index c13dee7..9b2dd47 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -135,7 +135,7 @@ android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
- applicationId "com.rndiffapp"
+ applicationId "com.jellyfinaudioplayer"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 8
@@ -300,6 +300,10 @@ if (isNewArchitectureEnabled()) {
substitute(module("com.facebook.react:react-native"))
.using(project(":ReactAndroid")).because("On New Architecture we're building React Native from source")
}
+ resolutionStrategy {
+ force 'com.google.android.exoplayer:exoplayer-core:2.11.4'
+ }
+
}
}
diff --git a/android/app/src/main/java/com/jellyfinaudioplayer/MainApplication.java b/android/app/src/main/java/com/jellyfinaudioplayer/MainApplication.java
index 1aeeec7..3da7d92 100644
--- a/android/app/src/main/java/com/jellyfinaudioplayer/MainApplication.java
+++ b/android/app/src/main/java/com/jellyfinaudioplayer/MainApplication.java
@@ -11,6 +11,9 @@ import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
+import com.facebook.react.bridge.JSIModulePackage;
+import com.swmansion.reanimated.ReanimatedJSIModulePackage;
+
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
@@ -33,6 +36,11 @@ public class MainApplication extends Application implements ReactApplication {
protected String getJSMainModuleName() {
return "index";
}
+
+ @Override
+ protected JSIModulePackage getJSIModulePackage() {
+ return new ReanimatedJSIModulePackage();
+ }
};
@Override
diff --git a/android/build.gradle b/android/build.gradle
index d2786b4..19b45a5 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -57,6 +57,7 @@ allprojects {
jcenter() {
content {
includeGroup("com.google.android.exoplayer")
+ includeGroupByRegex("com.eightbitlab.*")
}
}
}
diff --git a/babel.config.js b/babel.config.js
index 13ea02c..096f8c5 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -31,6 +31,7 @@ module.exports = {
],
[
'module:react-native-dotenv'
- ]
+ ],
+ 'react-native-reanimated/plugin'
]
};
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index a8403a1..9439ba9 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -422,6 +422,33 @@ PODS:
- React-Core
- RNLocalize (2.2.1):
- React-Core
+ - RNReanimated (2.8.0):
+ - DoubleConversion
+ - FBLazyVector
+ - FBReactNativeSpec
+ - glog
+ - RCT-Folly
+ - RCTRequired
+ - RCTTypeSafety
+ - React-callinvoker
+ - React-Core
+ - React-Core/DevSupport
+ - React-Core/RCTWebSocket
+ - React-CoreModules
+ - React-cxxreact
+ - React-jsi
+ - React-jsiexecutor
+ - React-jsinspector
+ - React-RCTActionSheet
+ - React-RCTAnimation
+ - React-RCTBlob
+ - React-RCTImage
+ - React-RCTLinking
+ - React-RCTNetwork
+ - React-RCTSettings
+ - React-RCTText
+ - ReactCommon/turbomodule/core
+ - Yoga
- RNScreens (3.13.1):
- React-Core
- React-RCTImage
@@ -516,6 +543,7 @@ DEPENDENCIES:
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNLocalize (from `../node_modules/react-native-localize`)
+ - RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- "RNSentry (from `../node_modules/@sentry/react-native`)"
- RNSVG (from `../node_modules/react-native-svg`)
@@ -637,6 +665,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-gesture-handler"
RNLocalize:
:path: "../node_modules/react-native-localize"
+ RNReanimated:
+ :path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSentry:
@@ -707,6 +737,7 @@ SPEC CHECKSUMS:
RNFS: fc610f78fdf8bfc89a9e5cc2f898519f4dba1002
RNGestureHandler: 4f4986408310a43f1606c391f38f76e0d6e790d5
RNLocalize: cbcb55d0e19c78086ea4eea20e03fe8000bbbced
+ RNReanimated: 64573e25e078ae6bec03b891586d50b9ec284393
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
RNSentry: 2cd1daa124b0d9fd0dfc2cb6094fdd168cb579bc
RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8
diff --git a/package-lock.json b/package-lock.json
index 5619bca..f96afa8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,6 +26,7 @@
"date-fns": "^2.28.0",
"events": "^3.3.0",
"fuse.js": "^6.6.0",
+ "hermes-engine": "^0.11.0",
"i18n-js": "^3.9.2",
"lodash": "^4.17.21",
"react": "^17.0.2",
@@ -38,12 +39,13 @@
"react-native-fs": "^2.19.0",
"react-native-gesture-handler": "^2.4.1",
"react-native-localize": "^2.2.1",
+ "react-native-reanimated": "^2.8.0",
"react-native-safe-area-context": "^4.2.5",
"react-native-screens": "^3.13.1",
"react-native-shadow-2": "^6.0.5",
"react-native-svg": "^12.3.0",
"react-native-svg-transformer": "^1.0.0",
- "react-native-track-player": "^2.1.3",
+ "react-native-track-player": "^2.2.0-rc3",
"react-native-webview": "^11.18.2",
"react-redux": "^7.2.6",
"redux": "^4.2.0",
@@ -4911,8 +4913,7 @@
"node_modules/@types/invariant": {
"version": "2.2.35",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz",
- "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==",
- "peer": true
+ "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg=="
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.3",
@@ -8772,6 +8773,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/hermes-engine": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz",
+ "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw=="
+ },
"node_modules/hermes-estree": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.5.0.tgz",
@@ -12456,8 +12462,7 @@
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
- "peer": true
+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -14427,7 +14432,6 @@
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.8.0.tgz",
"integrity": "sha512-kJvf/UWLBMaGCs9X66MKq5zdFMgwx8D0nHnolbHR7s8ZnbLdb7TlQ/yuzIXqn/9wABfnwtNRI3CyaP1aHWMmZg==",
- "peer": true,
"dependencies": {
"@babel/plugin-transform-object-assign": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
@@ -14505,9 +14509,9 @@
}
},
"node_modules/react-native-track-player": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/react-native-track-player/-/react-native-track-player-2.1.3.tgz",
- "integrity": "sha512-JWKFRu+hr1ECN339RH+c+XDM7HfwvdvS4H1p4cJbhg/9b1CQGPJSrYXEhYkngN0msoxBxAjFyFIhjT2fWDCltA==",
+ "version": "2.2.0-rc3",
+ "resolved": "https://registry.npmjs.org/react-native-track-player/-/react-native-track-player-2.2.0-rc3.tgz",
+ "integrity": "sha512-Pvmum3MQ5PE8/yOIIPsk00zZk3EzdocUuVUwuBKSCmdKK/3O9YhnZRC3EuT59XCDm23pZZJZDSR44VeXN6Gamg==",
"peerDependencies": {
"react": ">=16.8.6",
"react-native": ">=0.60.0-rc.2",
@@ -14806,11 +14810,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-native/node_modules/hermes-engine": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz",
- "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw=="
- },
"node_modules/react-native/node_modules/hermes-parser": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.5.0.tgz",
@@ -15879,8 +15878,7 @@
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
- "peer": true
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
},
"node_modules/setprototypeof": {
"version": "1.1.1",
@@ -16385,8 +16383,7 @@
"node_modules/string-hash-64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-hash-64/-/string-hash-64-1.0.3.tgz",
- "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==",
- "peer": true
+ "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw=="
},
"node_modules/string-length": {
"version": "4.0.2",
@@ -20895,8 +20892,7 @@
"@types/invariant": {
"version": "2.2.35",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz",
- "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==",
- "peer": true
+ "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
@@ -23834,6 +23830,11 @@
}
}
},
+ "hermes-engine": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz",
+ "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw=="
+ },
"hermes-estree": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.5.0.tgz",
@@ -26684,8 +26685,7 @@
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
- "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
- "peer": true
+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"lodash.merge": {
"version": "4.6.2",
@@ -28377,11 +28377,6 @@
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz",
"integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA=="
},
- "hermes-engine": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz",
- "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw=="
- },
"hermes-parser": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.5.0.tgz",
@@ -28817,7 +28812,6 @@
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-2.8.0.tgz",
"integrity": "sha512-kJvf/UWLBMaGCs9X66MKq5zdFMgwx8D0nHnolbHR7s8ZnbLdb7TlQ/yuzIXqn/9wABfnwtNRI3CyaP1aHWMmZg==",
- "peer": true,
"requires": {
"@babel/plugin-transform-object-assign": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
@@ -28871,9 +28865,9 @@
}
},
"react-native-track-player": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/react-native-track-player/-/react-native-track-player-2.1.3.tgz",
- "integrity": "sha512-JWKFRu+hr1ECN339RH+c+XDM7HfwvdvS4H1p4cJbhg/9b1CQGPJSrYXEhYkngN0msoxBxAjFyFIhjT2fWDCltA==",
+ "version": "2.2.0-rc3",
+ "resolved": "https://registry.npmjs.org/react-native-track-player/-/react-native-track-player-2.2.0-rc3.tgz",
+ "integrity": "sha512-Pvmum3MQ5PE8/yOIIPsk00zZk3EzdocUuVUwuBKSCmdKK/3O9YhnZRC3EuT59XCDm23pZZJZDSR44VeXN6Gamg==",
"requires": {}
},
"react-native-webview": {
@@ -29401,8 +29395,7 @@
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
- "peer": true
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
},
"setprototypeof": {
"version": "1.1.1",
@@ -29816,8 +29809,7 @@
"string-hash-64": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-hash-64/-/string-hash-64-1.0.3.tgz",
- "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==",
- "peer": true
+ "integrity": "sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw=="
},
"string-length": {
"version": "4.0.2",
diff --git a/package.json b/package.json
index 0a7d57c..2df2cf3 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"date-fns": "^2.28.0",
"events": "^3.3.0",
"fuse.js": "^6.6.0",
+ "hermes-engine": "^0.11.0",
"i18n-js": "^3.9.2",
"lodash": "^4.17.21",
"react": "^17.0.2",
@@ -42,12 +43,13 @@
"react-native-fs": "^2.19.0",
"react-native-gesture-handler": "^2.4.1",
"react-native-localize": "^2.2.1",
+ "react-native-reanimated": "^2.8.0",
"react-native-safe-area-context": "^4.2.5",
"react-native-screens": "^3.13.1",
"react-native-shadow-2": "^6.0.5",
"react-native-svg": "^12.3.0",
"react-native-svg-transformer": "^1.0.0",
- "react-native-track-player": "^2.1.3",
+ "react-native-track-player": "^2.2.0-rc3",
"react-native-webview": "^11.18.2",
"react-redux": "^7.2.6",
"redux": "^4.2.0",
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 712c699..f56b808 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -12,6 +12,7 @@ import {
import { useColorScheme } from 'react-native';
import { ColorSchemeContext, themes } from './Colors';
import DownloadManager from './DownloadManager';
+import { GestureHandlerRootView } from 'react-native-gesture-handler';
// import ErrorReportingAlert from 'utility/ErrorReportingAlert';
export default function App(): JSX.Element {
@@ -30,7 +31,8 @@ export default function App(): JSX.Element {
Capability.SkipToPrevious,
Capability.Stop,
Capability.SeekTo,
- ]
+ ],
+ stopWithApp: true
});
}
setupTrackPlayer();
@@ -41,8 +43,10 @@ export default function App(): JSX.Element {
-
-
+
+
+
+
diff --git a/src/components/Progresstrack.tsx b/src/components/Progresstrack.tsx
new file mode 100644
index 0000000..31ed454
--- /dev/null
+++ b/src/components/Progresstrack.tsx
@@ -0,0 +1,52 @@
+import { THEME_COLOR } from 'CONSTANTS';
+import styled from 'styled-components/native';
+import Animated from 'react-native-reanimated';
+
+export function getSeconds(seconds: number): string {
+ 'worklet';
+ return Math.floor(seconds % 60).toString().padStart(2, '0');
+}
+
+export function getMinutes(seconds: number): number {
+ 'worklet';
+ return Math.floor(seconds / 60);
+}
+
+export function calculateProgressTranslation(
+ position: number,
+ reference: number,
+ width: number,
+) {
+ 'worklet';
+ const completion = position / reference;
+ const output = (1 - (completion || 0)) * -1 * width;
+ return output;
+}
+
+export const ProgressTrackContainer = styled.View`
+ overflow: hidden;
+ height: 5px;
+ flex: 1;
+ flex-direction: row;
+ align-items: center;
+ position: relative;
+ border-radius: 6px;
+`;
+
+export interface ProgressTrackProps {
+ opacity?: number;
+ stroke?: number;
+}
+
+const ProgressTrack = styled(Animated.View)`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: ${(props) => props.stroke ? props.stroke + 'px' : '100%'};
+ background-color: ${THEME_COLOR};
+ opacity: ${(props) => props.opacity || 1};
+ border-radius: 99px;
+`;
+
+export default ProgressTrack;
\ No newline at end of file
diff --git a/src/components/ReText.tsx b/src/components/ReText.tsx
new file mode 100644
index 0000000..51db0d3
--- /dev/null
+++ b/src/components/ReText.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import type { TextProps as RNTextProps } from 'react-native';
+import { StyleSheet, TextInput } from 'react-native';
+import Animated, { useAnimatedProps } from 'react-native-reanimated';
+
+const styles = StyleSheet.create({
+ baseStyle: {
+ color: 'black',
+ },
+});
+Animated.addWhitelistedNativeProps({ text: true });
+
+interface TextProps {
+ text: Animated.SharedValue;
+ style?: Animated.AnimateProps['style'];
+}
+
+const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
+
+const ReText = (props: TextProps) => {
+ const { text, style } = { style: {}, ...props };
+ const animatedProps = useAnimatedProps(() => {
+ return {
+ text: text.value,
+ // Here we use any because the text prop is not available in the type
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any;
+ });
+ return (
+
+ );
+};
+
+export default ReText;
\ No newline at end of file
diff --git a/src/components/Typography.ts b/src/components/Typography.ts
index abad679..ae6416d 100644
--- a/src/components/Typography.ts
+++ b/src/components/Typography.ts
@@ -3,12 +3,12 @@ import Text from './Text';
export const Header = styled(Text)`
margin: 0 0 6px 0;
- font-size: 24px;
+ font-size: 28px;
font-weight: 400;
`;
export const SubHeader = styled(Text)`
- font-size: 14px;
+ font-size: 16px;
margin: 0 0 6px 0;
font-weight: 400;
opacity: 0.5;
diff --git a/src/screens/Music/overlays/NowPlaying/index.tsx b/src/screens/Music/overlays/NowPlaying/index.tsx
index 50b1251..c8a9982 100644
--- a/src/screens/Music/overlays/NowPlaying/index.tsx
+++ b/src/screens/Music/overlays/NowPlaying/index.tsx
@@ -7,12 +7,13 @@ import PlayIcon from 'assets/icons/play.svg';
import PauseIcon from 'assets/icons/pause.svg';
import useCurrentTrack from 'utility/useCurrentTrack';
import TrackPlayer, { State, usePlaybackState, useProgress } from 'react-native-track-player';
-import { THEME_COLOR } from 'CONSTANTS';
import { Shadow } from 'react-native-shadow-2';
import usePrevious from 'utility/usePrevious';
import Text from 'components/Text';
import useDefaultStyles, { ColoredBlurView } from 'components/Colors';
import { useNavigation } from '@react-navigation/native';
+import { calculateProgressTranslation } from 'components/Progresstrack';
+import { THEME_COLOR } from 'CONSTANTS';
const NOW_PLAYING_POPOVER_MARGIN = 6;
const NOW_PLAYING_POPOVER_WIDTH = Dimensions.get('screen').width - 2 * NOW_PLAYING_POPOVER_MARGIN;
@@ -38,6 +39,17 @@ const InnerContainer = styled.Pressable`
align-items: center;
`;
+const ProgressTrack = styled(Animated.View)<{ stroke?: number; opacity?: number}>`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: ${(props) => props.stroke ? props.stroke + 'px' : '100%'};
+ background-color: ${THEME_COLOR};
+ opacity: ${(props) => props.opacity || 1};
+ border-radius: 99px;
+`;
+
const ShadowOverlay = styled.View`
position: absolute;
top: 0;
@@ -61,21 +73,6 @@ const ActionButton = styled.Pressable`
margin-right: 8px;
`;
-interface ProgressTrackProps {
- opacity?: number;
-}
-
-const ProgressTrack = styled(Animated.View)`
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 2px;
- background-color: ${THEME_COLOR};
- opacity: ${(props) => props.opacity || 1};
- border-radius: 99px;
-`;
-
function SelectActionButton() {
const state = usePlaybackState();
const defaultStyles = useDefaultStyles();
@@ -108,11 +105,6 @@ function SelectActionButton() {
}
}
-function calculateProgressTranslation(position: number, reference: number) {
- const completion = position / reference;
- return (1 - (completion || 0)) * -1 * NOW_PLAYING_POPOVER_WIDTH;
-}
-
function NowPlaying() {
const { index, track } = useCurrentTrack();
const { buffered, duration, position } = useProgress();
@@ -130,13 +122,13 @@ function NowPlaying() {
const hasChangedTrack = previousIndex !== index || duration === 0;
Animated.timing(bufferAnimation.current, {
- toValue: calculateProgressTranslation(buffered, duration),
+ toValue: calculateProgressTranslation(buffered, duration, NOW_PLAYING_POPOVER_WIDTH),
duration: hasChangedTrack ? 0 : 500,
useNativeDriver: true,
easing: Easing.ease,
}).start();
Animated.timing(progressAnimation.current, {
- toValue: calculateProgressTranslation(position, duration),
+ toValue: calculateProgressTranslation(position, duration, NOW_PLAYING_POPOVER_WIDTH),
duration: hasChangedTrack ? 0 : 500,
useNativeDriver: true,
}).start();
@@ -168,9 +160,11 @@ function NowPlaying() {
diff --git a/src/screens/modals/Player/components/ProgressBar.tsx b/src/screens/modals/Player/components/ProgressBar.tsx
index e74791d..d925a5d 100644
--- a/src/screens/modals/Player/components/ProgressBar.tsx
+++ b/src/screens/modals/Player/components/ProgressBar.tsx
@@ -1,95 +1,187 @@
-import React, { Component } from 'react';
-import TrackPlayer from 'react-native-track-player';
+import React, { useEffect } from 'react';
+import TrackPlayer, { useProgress } from 'react-native-track-player';
import styled from 'styled-components/native';
-import { Text, Platform } from 'react-native';
-import Slider from '@react-native-community/slider';
+import ProgressTrack, {
+ calculateProgressTranslation,
+ getMinutes,
+ getSeconds,
+ ProgressTrackContainer
+} from 'components/Progresstrack';
+import { Gesture, GestureDetector, gestureHandlerRootHOC } from 'react-native-gesture-handler';
import { THEME_COLOR } from 'CONSTANTS';
-import { DefaultStylesProvider } from 'components/Colors';
+import Reanimated, {
+ useSharedValue,
+ useAnimatedStyle,
+ withTiming,
+ Easing,
+ useDerivedValue,
+ runOnJS,
+} from 'react-native-reanimated';
+import ReText from 'components/ReText';
+
+const DRAG_HANDLE_SIZE = 20;
+
+const Container = styled.View`
+ margin-top: 28px;
+`;
const NumberBar = styled.View`
flex-direction: row;
justify-content: space-between;
width: 100%;
- padding: 20px 0;
+ padding: 8px 0;
`;
-function getSeconds(seconds: number): string {
- return Math.floor(seconds % 60).toString().padStart(2, '0');
-}
+const Number = styled(ReText)`
+ font-size: 13px;
+`;
-function getMinutes(seconds: number): number {
- return Math.floor(seconds / 60);
-}
+const DragHandle = styled(Reanimated.View)`
+ width: ${DRAG_HANDLE_SIZE}px;
+ height: ${DRAG_HANDLE_SIZE}px;
+ border-radius: ${DRAG_HANDLE_SIZE}px;
+ background-color: ${THEME_COLOR};
+ position: absolute;
+ left: -${DRAG_HANDLE_SIZE / 2}px;
+ top: -${DRAG_HANDLE_SIZE / 2 - 2.5}px;
+ z-index: 14;
+`;
-interface State {
- position: number;
- duration: number;
- gesture?: number;
-}
+function ProgressBar() {
+ const { position, buffered, duration } = useProgress();
-export default class ProgressBar extends Component<{}, State> {
- state: State = {
- position: 0,
- duration: 0,
- };
+ const width = useSharedValue(0);
+ const pos = useSharedValue(0);
+ const buf = useSharedValue(0);
+ const dur = useSharedValue(0);
- timer: number | null = null;
+ const isDragging = useSharedValue(false);
+ const offset = useSharedValue(0);
- componentDidMount() {
- this.timer = setInterval(this.updateProgress, 500);
- }
+ const bufferAnimation = useDerivedValue(() => {
+ return calculateProgressTranslation(buf.value, dur.value, width.value);
+ }, [[dur, buf, width.value]]);
- componentWillUnmount() {
- if (this.timer) {
- clearInterval(this.timer);
+ const progressAnimation = useDerivedValue(() => {
+ if (isDragging.value) {
+ return calculateProgressTranslation(offset.value, width.value, width.value);
+ } else {
+ return calculateProgressTranslation(pos.value, dur.value, width.value);
}
- }
+ });
- updateProgress = async () => {
- const [position, duration] = await Promise.all([
- TrackPlayer.getPosition(),
- TrackPlayer.getDuration(),
- ]);
+ const timePassed = useDerivedValue(() => {
+ if (isDragging.value) {
+ const currentPosition = (offset.value - DRAG_HANDLE_SIZE / 2) / (width.value - DRAG_HANDLE_SIZE) * dur.value;
+ return getMinutes(currentPosition) + ':' + getSeconds(currentPosition);
+ } else {
+ return getMinutes(pos.value) + ':' + getSeconds(pos.value);
+ }
+ }, [pos]);
- this.setState({ position, duration });
- };
+ const timeRemaining = useDerivedValue(() => {
+ if (isDragging.value) {
+ const currentPosition = (offset.value - DRAG_HANDLE_SIZE / 2) / (width.value - DRAG_HANDLE_SIZE) * dur.value;
+ const remaining = (currentPosition - dur.value) * -1;
+ return `-${getMinutes(remaining)}:${getSeconds((remaining))}`;
+ } else {
+ const remaining = (pos.value - dur.value) * -1;
+ return `-${getMinutes(remaining)}:${getSeconds((remaining))}`;
+ }
+ }, [pos, dur]);
+
+ const gesture = Gesture.Pan()
+ .onBegin(() => {
+ isDragging.value = true;
+ }).onUpdate((e) => {
+ offset.value = Math.min(Math.max(DRAG_HANDLE_SIZE / 2, e.x), width.value - DRAG_HANDLE_SIZE / 2);
+ }).onFinalize(() => {
+ pos.value = (offset.value - DRAG_HANDLE_SIZE / 2) / (width.value - DRAG_HANDLE_SIZE) * dur.value;
+ isDragging.value = false;
+ runOnJS(TrackPlayer.seekTo)(pos.value);
+ });
- handleGesture = async (gesture: number) => {
- // Set relative translation in state
- this.setState({ gesture });
- };
+ useEffect(() => {
+ pos.value = position;
+ buf.value = buffered;
+ dur.value = duration;
+ }, [position, buffered, duration]);
- handleEndOfGesture = (position: number) => {
- // Calculate and set the new position
- TrackPlayer.seekTo(position);
- this.setState({ gesture: undefined, position });
- };
-
- render() {
- const { position, duration, gesture } = this.state;
-
- return (
-
- {defaultStyle => (
- <>
-
-
- {getMinutes(gesture || position)}:{getSeconds(gesture || position)}
- {getMinutes(duration)}:{getSeconds(duration)}
-
- >
- )
+ const dragHandleStyles = useAnimatedStyle(() => {
+ return {
+ transform: [
+ { translateX: offset.value },
+ {
+ scale: withTiming(isDragging.value ? 1 : 0.05, {
+ duration: 100,
+ easing: Easing.out(Easing.ease),
+ })
}
-
- );
- }
-}
\ No newline at end of file
+ ],
+ };
+ });
+
+ const bufferStyles = useAnimatedStyle(() => ({
+ transform: [
+ { translateX: bufferAnimation.value }
+ ]
+ }));
+
+ const progressStyles = useAnimatedStyle(() => {
+ return {
+ transform: [
+ { translateX: progressAnimation.value }
+ ]
+ };
+ });
+
+ const timePassedStyles = useAnimatedStyle(() => {
+ return {
+ transform: [
+ { translateY: withTiming(isDragging.value && offset.value < 48 ? 12 : 0, {
+ duration: 145,
+ easing: Easing.ease
+ }) },
+ ],
+ };
+ });
+
+ const timeRemainingStyles = useAnimatedStyle(() => {
+ return {
+ transform: [
+ { translateY: withTiming(isDragging.value && offset.value > width.value - 48 ? 12 : 0, {
+ duration: 150,
+ easing: Easing.ease
+ }) },
+ ],
+ };
+ });
+
+ return (
+ { width.value = e.nativeEvent.layout.width; }}>
+
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+
+
+ );
+}
+
+export default gestureHandlerRootHOC(ProgressBar);
diff --git a/src/screens/modals/Player/index.tsx b/src/screens/modals/Player/index.tsx
index fbefb89..e545e3f 100644
--- a/src/screens/modals/Player/index.tsx
+++ b/src/screens/modals/Player/index.tsx
@@ -21,8 +21,8 @@ export default function Player() {
-
+
);