created the slider with text
This commit is contained in:
parent
186ab338e4
commit
f344c9e97f
|
@ -0,0 +1,98 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { LayoutChangeEvent, View, TextInput } from 'react-native';
|
||||||
|
import { PanGestureHandler } from 'react-native-gesture-handler';
|
||||||
|
import Animated, {
|
||||||
|
useAnimatedGestureHandler,
|
||||||
|
useAnimatedProps,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useDerivedValue,
|
||||||
|
useSharedValue
|
||||||
|
} from 'react-native-reanimated';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
import { useTheme } from '../../../../theme';
|
||||||
|
|
||||||
|
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
|
||||||
|
|
||||||
|
const Slider = ({ currentTime = 0, duration = 120, thumbColor = '' }) => {
|
||||||
|
console.log('🚀 ~ file: Slider.tsx:18 ~ Slider ~ currentTime:', currentTime);
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
const maxWidth = useSharedValue(1);
|
||||||
|
const x = useSharedValue(currentTime);
|
||||||
|
const current = useSharedValue('00:00');
|
||||||
|
const scale = useSharedValue(1);
|
||||||
|
|
||||||
|
const styleLine = useAnimatedStyle(() => ({
|
||||||
|
width: x.value,
|
||||||
|
zIndex: 2
|
||||||
|
}));
|
||||||
|
|
||||||
|
const styleThumb = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ translateX: x.value }, { scale: scale.value }]
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onLayout = (event: LayoutChangeEvent) => {
|
||||||
|
const { width } = event.nativeEvent.layout;
|
||||||
|
maxWidth.value = width - 12;
|
||||||
|
};
|
||||||
|
|
||||||
|
const gestureHandler = useAnimatedGestureHandler({
|
||||||
|
onStart: (_, ctx: any) => {
|
||||||
|
ctx.startX = x.value;
|
||||||
|
},
|
||||||
|
onActive: (event, ctx: any) => {
|
||||||
|
const moveInX: number = ctx.startX + event.translationX;
|
||||||
|
if (moveInX < 0) {
|
||||||
|
x.value = 0;
|
||||||
|
} else if (moveInX > maxWidth.value) {
|
||||||
|
x.value = maxWidth.value;
|
||||||
|
} else {
|
||||||
|
x.value = moveInX;
|
||||||
|
}
|
||||||
|
|
||||||
|
scale.value = 1.3;
|
||||||
|
},
|
||||||
|
onEnd: () => {
|
||||||
|
scale.value = 1;
|
||||||
|
// SEND A CALLBACK TO CHANGE THE PROGRESS OF THE AUDIO
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useDerivedValue(() => {
|
||||||
|
const cTime = (x.value * duration) / maxWidth.value;
|
||||||
|
const minutes = Math.floor(cTime / 60);
|
||||||
|
const remainingSeconds = Math.floor(cTime % 60);
|
||||||
|
const formattedMinutes = String(minutes).padStart(2, '0');
|
||||||
|
const formattedSeconds = String(remainingSeconds).padStart(2, '0');
|
||||||
|
current.value = `${formattedMinutes}:${formattedSeconds}`;
|
||||||
|
}, [x, maxWidth, duration]);
|
||||||
|
|
||||||
|
const getCurrentTime = useAnimatedProps(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
text: current.value
|
||||||
|
} as any),
|
||||||
|
[current]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.sliderContainer}>
|
||||||
|
<AnimatedTextInput
|
||||||
|
defaultValue={'00:00'}
|
||||||
|
editable={false}
|
||||||
|
style={[styles.duration, { color: colors.bodyText }]}
|
||||||
|
animatedProps={getCurrentTime}
|
||||||
|
/>
|
||||||
|
<View style={styles.slider} onLayout={onLayout}>
|
||||||
|
<View style={[styles.line, { backgroundColor: colors.auxiliaryText }]} />
|
||||||
|
<Animated.View style={[styles.line, styleLine, { backgroundColor: colors.tintColor }]} />
|
||||||
|
<PanGestureHandler onGestureEvent={gestureHandler}>
|
||||||
|
<Animated.View style={[styles.thumbSlider, { backgroundColor: thumbColor }, styleThumb]} />
|
||||||
|
</PanGestureHandler>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Slider;
|
|
@ -1,17 +1,13 @@
|
||||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { StyleProp, StyleSheet, Text, TextStyle, View, useWindowDimensions } from 'react-native';
|
import { StyleProp, TextStyle, View } from 'react-native';
|
||||||
import { Audio, AVPlaybackStatus, InterruptionModeAndroid, InterruptionModeIOS } from 'expo-av';
|
import { Audio, AVPlaybackStatus, InterruptionModeAndroid, InterruptionModeIOS } from 'expo-av';
|
||||||
import Slider from '@react-native-community/slider';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { activateKeepAwakeAsync, deactivateKeepAwake } from 'expo-keep-awake';
|
import { activateKeepAwakeAsync, deactivateKeepAwake } from 'expo-keep-awake';
|
||||||
import { Sound } from 'expo-av/build/Audio/Sound';
|
import { Sound } from 'expo-av/build/Audio/Sound';
|
||||||
|
|
||||||
import Touchable from '../../Touchable';
|
import Touchable from '../../Touchable';
|
||||||
import Markdown from '../../../markdown';
|
import Markdown from '../../../markdown';
|
||||||
import { CustomIcon } from '../../../CustomIcon';
|
import { CustomIcon } from '../../../CustomIcon';
|
||||||
import sharedStyles from '../../../../views/Styles';
|
|
||||||
import { themes } from '../../../../lib/constants';
|
import { themes } from '../../../../lib/constants';
|
||||||
import { isAndroid, isIOS } from '../../../../lib/methods/helpers';
|
|
||||||
import MessageContext from '../../Context';
|
import MessageContext from '../../Context';
|
||||||
import ActivityIndicator from '../../../ActivityIndicator';
|
import ActivityIndicator from '../../../ActivityIndicator';
|
||||||
import { TGetCustomEmoji } from '../../../../definitions/IEmoji';
|
import { TGetCustomEmoji } from '../../../../definitions/IEmoji';
|
||||||
|
@ -21,6 +17,8 @@ import { downloadMediaFile, getMediaCache } from '../../../../lib/methods/handle
|
||||||
import EventEmitter from '../../../../lib/methods/helpers/events';
|
import EventEmitter from '../../../../lib/methods/helpers/events';
|
||||||
import { PAUSE_AUDIO } from '../../constants';
|
import { PAUSE_AUDIO } from '../../constants';
|
||||||
import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference';
|
import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference';
|
||||||
|
import styles from './styles';
|
||||||
|
import Slider from './Slider';
|
||||||
|
|
||||||
interface IButton {
|
interface IButton {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
@ -48,36 +46,6 @@ const mode = {
|
||||||
interruptionModeAndroid: InterruptionModeAndroid.DoNotMix
|
interruptionModeAndroid: InterruptionModeAndroid.DoNotMix
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
audioContainer: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: 56,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 4,
|
|
||||||
marginBottom: 6
|
|
||||||
},
|
|
||||||
playPauseButton: {
|
|
||||||
marginHorizontal: 10,
|
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
},
|
|
||||||
audioLoading: {
|
|
||||||
marginHorizontal: 8
|
|
||||||
},
|
|
||||||
slider: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
duration: {
|
|
||||||
marginHorizontal: 12,
|
|
||||||
fontSize: 14,
|
|
||||||
...sharedStyles.textRegular
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const formatTime = (seconds: number) => moment.utc(seconds * 1000).format('mm:ss');
|
|
||||||
|
|
||||||
const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 };
|
const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 };
|
||||||
|
|
||||||
const Button = React.memo(({ loading, paused, onPress, disabled, cached }: IButton) => {
|
const Button = React.memo(({ loading, paused, onPress, disabled, cached }: IButton) => {
|
||||||
|
@ -114,7 +82,6 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style }: IMessage
|
||||||
const [cached, setCached] = useState(false);
|
const [cached, setCached] = useState(false);
|
||||||
|
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
const { scale } = useWindowDimensions();
|
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const sound = useRef<Sound | null>(null);
|
const sound = useRef<Sound | null>(null);
|
||||||
|
@ -150,6 +117,7 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style }: IMessage
|
||||||
const onProgress = (data: AVPlaybackStatus) => {
|
const onProgress = (data: AVPlaybackStatus) => {
|
||||||
if (data.isLoaded) {
|
if (data.isLoaded) {
|
||||||
const currentTime = data.positionMillis / 1000;
|
const currentTime = data.positionMillis / 1000;
|
||||||
|
console.log('🚀 ~ file: index.tsx:120 ~ onProgress ~ currentTime:', currentTime);
|
||||||
if (currentTime <= duration) {
|
if (currentTime <= duration) {
|
||||||
setCurrentTime(currentTime);
|
setCurrentTime(currentTime);
|
||||||
}
|
}
|
||||||
|
@ -171,8 +139,6 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style }: IMessage
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDuration = () => formatTime(currentTime || duration);
|
|
||||||
|
|
||||||
const togglePlayPause = () => {
|
const togglePlayPause = () => {
|
||||||
setPaused(!paused);
|
setPaused(!paused);
|
||||||
playPause(!paused);
|
playPause(!paused);
|
||||||
|
@ -240,15 +206,6 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style }: IMessage
|
||||||
handleDownload();
|
handleDownload();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onValueChange = async (value: number) => {
|
|
||||||
try {
|
|
||||||
setCurrentTime(value);
|
|
||||||
await sound.current?.setPositionAsync(value * 1000);
|
|
||||||
} catch {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sound.current = new Audio.Sound();
|
sound.current = new Audio.Sound();
|
||||||
sound.current?.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
|
sound.current?.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
|
||||||
|
@ -296,9 +253,9 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style }: IMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
let thumbColor;
|
let thumbColor;
|
||||||
if (isAndroid && isReply) {
|
if (isReply) {
|
||||||
thumbColor = themes[theme].tintDisabled;
|
thumbColor = themes[theme].tintDisabled;
|
||||||
} else if (isAndroid) {
|
} else {
|
||||||
thumbColor = themes[theme].tintColor;
|
thumbColor = themes[theme].tintColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,19 +275,8 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style }: IMessage
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Button disabled={isReply} loading={loading} paused={paused} cached={cached} onPress={onPress} />
|
<Button disabled={isReply} loading={loading} paused={paused} cached={cached} onPress={onPress} />
|
||||||
<Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{getDuration()}</Text>
|
<Slider currentTime={currentTime} duration={duration} thumbColor={thumbColor} />
|
||||||
<Slider
|
<View style={{ width: 36, height: 24, backgroundColor: '#999', borderRadius: 4, marginRight: 16 }} />
|
||||||
disabled={isReply}
|
|
||||||
style={styles.slider}
|
|
||||||
value={currentTime}
|
|
||||||
maximumValue={duration}
|
|
||||||
minimumValue={0}
|
|
||||||
thumbTintColor={thumbColor}
|
|
||||||
minimumTrackTintColor={themes[theme].tintColor}
|
|
||||||
maximumTrackTintColor={themes[theme].auxiliaryText}
|
|
||||||
onValueChange={onValueChange}
|
|
||||||
thumbImage={isIOS ? { uri: 'audio_thumb', scale } : undefined}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
import sharedStyles from '../../../../views/Styles';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
audioContainer: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: 56,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
marginBottom: 6
|
||||||
|
},
|
||||||
|
playPauseButton: {
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#929',
|
||||||
|
marginLeft: 16,
|
||||||
|
height: 32,
|
||||||
|
width: 32,
|
||||||
|
borderRadius: 4,
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
audioLoading: {
|
||||||
|
marginHorizontal: 8
|
||||||
|
},
|
||||||
|
sliderContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%'
|
||||||
|
},
|
||||||
|
slider: {
|
||||||
|
marginRight: 12,
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flex: 1
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
height: 4,
|
||||||
|
borderRadius: 2,
|
||||||
|
zIndex: 1,
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
width: 40,
|
||||||
|
marginHorizontal: 12,
|
||||||
|
fontSize: 14,
|
||||||
|
...sharedStyles.textRegular
|
||||||
|
},
|
||||||
|
thumbSlider: {
|
||||||
|
height: 12,
|
||||||
|
width: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
zIndex: 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default styles;
|
Loading…
Reference in New Issue