diff --git a/apps/common-app/src/App.tsx b/apps/common-app/src/App.tsx
index 4204e65ad..c9b6eb581 100644
--- a/apps/common-app/src/App.tsx
+++ b/apps/common-app/src/App.tsx
@@ -30,8 +30,7 @@ const HomeScreen: FC = () => {
style={({ pressed }) => [
styles.button,
{ borderStyle: pressed ? 'solid' : 'dashed' },
- ]}
- >
+ ]}>
{item.title}
{item.subtitle}
@@ -64,8 +63,7 @@ const App: FC = () => {
headerTintColor: colors.white,
headerBackTitle: ' ',
headerBackAccessibilityLabel: 'Go back',
- }}
- >
+ }}>
= (props) => {
return (
+ style={[
+ styles.basic,
+ centered && styles.centered,
+ !disablePadding && styles.padding,
+ style,
+ ]}>
{children}
diff --git a/apps/common-app/src/components/VerticalSlider.tsx b/apps/common-app/src/components/VerticalSlider.tsx
new file mode 100644
index 000000000..889ba92b6
--- /dev/null
+++ b/apps/common-app/src/components/VerticalSlider.tsx
@@ -0,0 +1,119 @@
+import React, { useEffect } from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+import { GestureDetector, Gesture } from 'react-native-gesture-handler';
+import Animated, {
+ useSharedValue,
+ useAnimatedStyle,
+} from 'react-native-reanimated';
+import { scheduleOnRN } from 'react-native-worklets';
+
+const SLIDER_HEIGHT = 150;
+const THUMB_SIZE = 30;
+const TRACK_HEIGHT = SLIDER_HEIGHT - THUMB_SIZE;
+
+interface VerticalSliderProps {
+ label: string;
+ value: number;
+ onValueChange: (val: number) => void;
+}
+
+const VerticalSlider: React.FC = ({
+ label,
+ value,
+ onValueChange,
+}) => {
+ const progress = useSharedValue(value);
+ const startValue = useSharedValue(0);
+
+ useEffect(() => {
+ progress.value = value;
+ }, [value, progress]);
+
+ const gesture = Gesture.Pan()
+ .onStart(() => {
+ 'worklet';
+ startValue.value = progress.value;
+ })
+ .onUpdate((e) => {
+ 'worklet';
+ const change = -e.translationY / TRACK_HEIGHT;
+ const newValue = startValue.value + change;
+ progress.value = Math.min(Math.max(newValue, 0), 1);
+ scheduleOnRN(onValueChange, progress.value);
+ });
+
+ const thumbStyle = useAnimatedStyle(() => {
+ const translateY = (1 - progress.value) * TRACK_HEIGHT;
+ return {
+ transform: [{ translateY }],
+ };
+ });
+
+ return (
+
+ {label}
+
+
+
+
+
+
+
+
+ {(value * 100).toFixed(0)}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ sliderContainer: {
+ alignItems: 'center',
+ gap: 5,
+ height: SLIDER_HEIGHT + 40,
+ },
+ sliderLabel: {
+ fontWeight: 'bold',
+ fontSize: 12,
+ color: '#333',
+ },
+ sliderTrackContainer: {
+ width: 40,
+ height: SLIDER_HEIGHT,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ sliderTrack: {
+ position: 'absolute',
+ width: 4,
+ height: '100%',
+ backgroundColor: '#111',
+ borderRadius: 2,
+ },
+ sliderThumbHitArea: {
+ position: 'absolute',
+ top: 0,
+ width: 40,
+ height: THUMB_SIZE,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ sliderThumb: {
+ width: 30,
+ height: 15,
+ backgroundColor: '#222',
+ borderWidth: 1,
+ borderColor: '#fff',
+ borderRadius: 2,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.5,
+ shadowRadius: 2,
+ },
+ sliderValue: {
+ fontSize: 10,
+ color: '#555',
+ fontVariant: ['tabular-nums'],
+ },
+});
+
+export default VerticalSlider;
diff --git a/apps/common-app/src/components/index.ts b/apps/common-app/src/components/index.ts
index 95b235bb7..c3e93660e 100644
--- a/apps/common-app/src/components/index.ts
+++ b/apps/common-app/src/components/index.ts
@@ -5,3 +5,4 @@ export { default as Switch } from './Switch';
export { default as Select } from './Select';
export { default as Container } from './Container';
export { default as BGGradient } from './BGGradient';
+export { default as VerticalSlider } from './VerticalSlider';
diff --git a/apps/common-app/src/examples/AudioFile/AudioPlayer.ts b/apps/common-app/src/examples/AudioFile/AudioPlayer.ts
index 91ef3695b..34a127013 100644
--- a/apps/common-app/src/examples/AudioFile/AudioPlayer.ts
+++ b/apps/common-app/src/examples/AudioFile/AudioPlayer.ts
@@ -1,4 +1,7 @@
-import { AudioContext, PlaybackNotificationManager } from 'react-native-audio-api';
+import {
+ AudioContext,
+ PlaybackNotificationManager,
+} from 'react-native-audio-api';
import type {
AudioBufferSourceNode,
AudioBuffer,
diff --git a/apps/common-app/src/examples/Distorted/Distorted.tsx b/apps/common-app/src/examples/Distorted/Distorted.tsx
new file mode 100644
index 000000000..579508c72
--- /dev/null
+++ b/apps/common-app/src/examples/Distorted/Distorted.tsx
@@ -0,0 +1,103 @@
+import React, { useCallback, useEffect, useState, FC } from 'react';
+import { ActivityIndicator } from 'react-native';
+import {
+ AudioContext,
+ AudioNode,
+ AudioBuffer,
+ AudioBufferSourceNode,
+} from 'react-native-audio-api';
+import { Container, Button } from '../../components';
+import { presetEffects } from '../../utils/effects';
+
+const URL = 'https://files.catbox.moe/s2i1wn.flac';
+
+const Distorted: FC = () => {
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+ const [buffer, setBuffer] = useState(null);
+
+ const aCtxRef = React.useRef(null);
+ const effectsMap = React.useRef