So I am making a music player and used react-native-video. For my output, I am able to play all the songs that I added it into the App.js where the data is Track. But I am able to change songs but not able to shuffle the music player when I press the forward button. Even when I toggled it on. I am not my error that does not allow me to shuffle them.
This is my Player Component
import Controls from './Controls';
import Video from 'react-native-video';
export default class Player extends Component {
constructor(props) {
super(props);
this.state = {
paused: true,
totalLength: 1,
currentPosition: 0,
selectedTrack: 0,
repeatOn: false,
shuffleOn: true,
};
}
setDuration(data) {
// console.log(totalLength);
this.setState({totalLength: Math.floor(data.duration)});
}
setTime(data) {
//console.log(data);
this.setState({currentPosition: Math.floor(data.currentTime)});
}
seek(time) {
time = Math.round(time);
this.refs.audioElement && this.refs.audioElement.seek(time);
this.setState({
currentPosition: time,
paused: false,
});
}
onBack() {
if (this.state.currentPosition < 10 && this.state.selectedTrack > 0) {
this.refs.audioElement && this.refs.audioElement.seek(0);
this.setState({ isChanging: true });
setTimeout(() => this.setState({
currentPosition: 0,
paused: false,
totalLength: 1,
isChanging: false,
selectedTrack: this.state.selectedTrack - 1,
}), 0);
} else {
this.refs.audioElement.seek(0);
this.setState({
currentPosition: 0,
});
}
}
onForward() {
if (this.state.selectedTrack < this.props.tracks.length - 1) {
this.refs.audioElement && this.refs.audioElement.seek(0);
this.setState({ isChanging: true });
setTimeout(() => this.setState({
currentPosition: 0,
totalLength: 1,
paused: false,
isChanging: false,
selectedTrack: this.state.selectedTrack + 1,
}), 0);
}
}
render() {
const track = this.props.tracks[this.state.selectedTrack];
const video = this.state.isChanging ? null : (
<Video source={{uri: track.url}} // Can be a URL or a local file.
ref="audioElement"
paused={this.state.paused} // Pauses playback entirely.
resizeMode="cover" // Fill the whole screen at aspect ratio.
repeat={false} // Repeat forever.
onLoadStart={this.loadStart} // Callback when video starts to load
onLoad={this.setDuration.bind(this)} // Callback when video loads
onProgress={this.setTime.bind(this)} // Callback every ~250ms with currentTime
onEnd={this.onEnd} // Callback when playback finishes
onError={this.videoError} // Callback when video cannot be loaded
style={styles.audioElement} />
);
return (
<View style={styles.container}>
<Controls
onPressRepeat={() => this.setState({repeatOn : !this.state.repeatOn})}
repeatOn={this.state.repeatOn}
shuffleOn={this.state.shuffleOn}
forwardDisabled={this.state.selectedTrack === this.props.tracks.length - 1}
onPressShuffle={() => this.setState({shuffleOn: !this.state.shuffleOn})}
onPressPlay={() => this.setState({paused: false})}
onPressPause={() => this.setState({paused: true})}
onBack={this.onBack.bind(this)}
onForward={this.onForward.bind(this)}
paused={this.state.paused}/>
{video}
</View>
);
}
}
This is my CSS
const styles = {
container: {
flex: 1,
backgroundColor: 'rgb(4,4,4)',
},
audioElement: {
height: 0,
width: 0,
}
};
And this is my App.js Component
import React, { Component } from 'react';
import Player from './Player';
export const TRACKS = [
{
id: '1',
url: 'http://tegos.kz/new/mp3_full/Post_Malone_-_Better_Now.mp3',
title: 'Better Now',
artist: 'Post Malone',
artwork: 'https://upload.wikimedia.org/wikipedia/en/thumb/c/c1/Beerbongs_%26_Bentleys_by_Post_Malone.png/220px-Beerbongs_%26_Bentleys_by_Post_Malone.png'
},
{
id: '2',
url: 'http://tegos.kz/new/mp3_full/Luis_Fonsi_feat._Daddy_Yankee_-_Despacito.mp3',
title: 'Despacito',
artist: 'Luis Fonsi',
artwork:'https://upload.wikimedia.org/wikipedia/en/c/c8/Luis_Fonsi_Feat._Daddy_Yankee_-_Despacito_%28Official_Single_Cover%29.png'
},
{
id: '3',
url: 'http://tegos.kz/new/mp3_full/Clean_Bandit_-_Solo_(feat._Demi_Lovato)_(Latin_Remix).mp3',
title: 'Solo (Latin Remix)',
artist: 'Clean Bandit feat. Demi Lovato',
},
{
id: '4',
url: 'http://tegos.kz/new/mp3_full/Greyson_Chance_-_Waiting_Outside_The_Lines.mp3',
title: 'Waiting Outside The Lines',
artist: 'Greyson Chance',
artwork: ''
},
];
export default class App extends Component {
render() {
return <Player tracks={TRACKS} />
}
}
If you got autoPlay variable(I got that in redux) , then just check below condition for auto play.
if(this.props.autoPlay){
if( this.state.currentPosition >= this.state.totalLength ){
this.onForward();
}
}
Related
How can I create a reusable React hook with animation style with Reanimated 2? I have an animation that is working on one element, but if I try to use the same animation on multiple elements on same screen only the first one registered is animating. It is too much animation code to duplicate it everywhere I need this animation, so how can I share this between multiple components on the same screen? And tips for making the animation simpler is also much appreciated.
import {useEffect} from 'react';
import {
cancelAnimation,
Easing,
useAnimatedStyle,
useSharedValue,
withRepeat,
withSequence,
withTiming,
} from 'react-native-reanimated';
const usePulseAnimation = ({shouldAnimate}: {shouldAnimate: boolean}) => {
const titleOpacity = useSharedValue(1);
const isAnimating = useSharedValue(false);
useEffect(() => {
if (shouldAnimate && !isAnimating.value) {
isAnimating.value = true;
titleOpacity.value = withRepeat(
withSequence(
withTiming(0.2, {duration: 700, easing: Easing.inOut(Easing.ease)}),
withTiming(
1,
{duration: 700, easing: Easing.inOut(Easing.ease)},
() => {
if (!shouldAnimate) {
cancelAnimation(titleOpacity);
}
},
),
),
-1,
false,
() => {
if (titleOpacity.value < 1) {
titleOpacity.value = withSequence(
withTiming(0.2, {
duration: 700,
easing: Easing.inOut(Easing.ease),
}),
withTiming(
1,
{duration: 700, easing: Easing.inOut(Easing.ease)},
() => {
isAnimating.value = false;
},
),
);
} else {
titleOpacity.value = withTiming(
1,
{
duration: 700,
easing: Easing.inOut(Easing.ease),
},
() => {
isAnimating.value = false;
},
);
}
},
);
} else {
isAnimating.value = false;
cancelAnimation(titleOpacity);
}
}, [shouldAnimate, isAnimating, titleOpacity]);
const pulseAnimationStyle = useAnimatedStyle(() => {
return {
opacity: titleOpacity.value,
};
});
return {pulseAnimationStyle, isAnimating: isAnimating.value};
};
export default usePulseAnimation;
And I am using it like this inside a component:
const {pulseAnimationStyle} = usePulseAnimation({
shouldAnimate: true,
});
return (
<Animated.View
style={[
{backgroundColor: 'white', height: 100, width: 100},
pulseAnimationStyle,
]}
/>
);
The approach that I've taken is to write my Animations as wrapper components.
This way you can build up a library of these animation components and then simply wrap whatever needs to be animated.
e.g.
//Wrapper component type:
export type ShakeProps = {
// Animation:
children: React.ReactNode;
repeat?: boolean;
repeatEvery?: number;
}
// Wrapper component:
const Shake: FC<ShakeProps> = ({
children,
repeat = false,
repeatEvery = 5000,
}) => {
const shiftY = useSharedValue(0);
const animatedStyles = useAnimatedStyle(() => ({
//Animation properties...
}));
const shake = () => {
//Update shared values...
}
// Loop every X seconds:
const repeatAnimation = () => {
shake();
setTimeout(() => {
repeatAnimation();
}, repeatEvery);
}
// Start Animations on component Init:
useEffect(() => {
// Run animation continously:
if(repeat){
repeatAnimation();
}
// OR ~ call once:
else{
shake();
}
}, []);
return (
<Animated.View style={[animatedStyles]}>
{children}
</Animated.View>
)
}
export default Shake;
Wrapper Component Usage:
import Shake from "../../util/animated-components/shake";
const Screen: FC = () => {
return (
<Shake repeat={true} repeatEvery={5000}>
{/* Whatever needs to be animated!...e.g. */}
<Text>Hello World!</Text>
</Shake>
)
}
From their docs:
CAUTION
Animated styles cannot be shared between views.
To work around this you can generate multiple useAnimatedStyle in top-level loop (number of iterations must be static, see React's Rules of Hooks for more information).
https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
I need some help please.
in one of my pages I call a component. However, it is not displayed. I'm not sure what's wrong, and how to find out where exactly the error came from. I know that the code contained in the component is good since I am calling it in another screen. I wonder if it's not because of 'parent' use
<View style={{width: "66.66%"}}>
<MapView parent={this} />
<Text>{"ICIII"}</Text>
</View>
There is just the text that is displayed (the text was here just to test). I hope someone can help me try to understand where the problem come from, I know I have a lot to learn so thank you for your time and explanations.
This is the component MapView :
export default class MapView extends Component {
constructor(props) {
super(props);
this.state = {
progress: new Animated.Value(0),
onLoading: true,
height: 0,
user_id: 0,
lang: "en",
access_token: "",
stats : "",
screen_updated: 0,
map_request_periods: "",
map_request_periods_title: "",
map_request_periods_val: "*"
};
}
performMessageFromWebView(e) {
if (e.nativeEvent && e.nativeEvent.data) {
let id = e.nativeEvent.data.replace('flightId=', '');
if (parseInt(id) > 0) {
this.props.navigation.navigate("UpdateTripsForm", {flightId: parseInt(id)}); // Open Screen UpdateTripForm.js
return true;
}
}
return false;
}
initUserData = async () => {
let user_id = await retrieveProfileUserId();
let lang = await retrieveAppLang();
let user_stats = await getUserFlightStats();
let access_token = await renewAccessToken();
if (user_id && lang && access_token && parseInt(user_id) > 0) {
this.setState({
user_id: parseInt(user_id),
lang: lang,
stats: user_stats,
access_token: access_token,
});
}
};
async UNSAFE_componentWillMount() {
this.initUserData();
}
async componentDidMount() {
// Refresh data
this.props.navigation.addListener("didFocus", async (payload) => {
let user_id = await retrieveProfileUserId();
let user_stats = await getUserFlightStats();
let access_token = await renewAccessToken();
if (user_id && access_token && parseInt(user_id) > 0) {
this.setState({
screen_updated: this.state.screen_updated + 1,
user_id: parseInt(user_id),
stats: user_stats,
access_token: access_token,
map_request_periods: "", // Force reinitialize choice
map_request_periods_title: "",
map_request_periods_val: "*"
});
console.log(Device.DeviceType);
}
// Force PORTRAIT orientation
await ScreenOrientation.getOrientationAsync().then(
(data_orientation) => {
if (parseInt(data_orientation) == parseInt(ScreenOrientation.Orientation.LANDSCAPE_LEFT) || parseInt(data_orientation) == parseInt(ScreenOrientation.Orientation.LANDSCAPE_RIGHT)) {
ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.PORTRAIT_UP
);
}
}
);
});
// const data = this.props.navigation.getParam('this.selectedStartDate', 'this.selectedEndDate');
// this.setState({ this.state.selectedStartDate, this.state.selectedEndDate});
Animated.timing(this.state.progress, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true,
}).start();
if (this.state.onLoading == true) {
setTimeout(
function () {
this.setState({ onLoading: false });
}.bind(this),
3000
);
}
}
render() {
return (
<View>
<WebView
geolocationEnabled={true}
javaScriptEnabled={true}
scrollEnabled={false}
overScrollMode="never"
source={{
uri:
"https://www.blzlva.org/fr/mestrips" +
"?society_id=" + API_SOCIETYID +
"&user_id=" + this.state.user_id +
"&lang=" + this.state.lang +
"&hidezoom=1" +
"&access_token=" + this.state.access_token +
"&screen=" + this.state.screen_updated
}}
originWhitelist={[
"https://www.blzblz.org",
"https://www.blabla.com"
]}
scalesPageToFit={true}
onMessage={(m) => this.performMessageFromWebView(m)}
style={{ marginHorizontal: 0, backgroundColor: "transparent" }}
/>
</View>
);
}
}
Import the map component which page you want to use it like following,
import Map from '../../map.js'
then use the component inside a view like following,
<View style={{width:100px}}>
<Map/>
</View>
I'm wondering if you could help me fix the following issue. Using the native-base datepicker I am trying to manipulate the default date. On load from the parent component I am setting it to today's date. Then using some logic to add x number of months. The date is then updated in the state, but it's not being mapped to the datepicker. See code below:
// Parent state from parent component
state = {
index: 0,
routes: [
{ key: '1', title: 'STEP 1' },
{ key: '2', title: 'STEP 2' },
{ key: '3', title: 'STEP 3' },
{ key: '4', title: 'STEP 4' }
],
purposes: [],
purpose_Of_Examination_Id: 0,
colours: [],
colour_Id: 0,
first_Examination: false,
installed_Correctly: null,
defect_Reason_Id: 0,
defect_Reasons: [],
hasDefects: false,
defect: '',
inspected_At: moment().toDate(),
next_Inspection_Date: moment().toDate()
}
// Child component
constructor(props: any) {
super(props);
this.state = this.props.parentState;
}
async componentDidMount() {
this.bindDates();
}
bindDates() {
var inspected_At = moment().toDate();
var next_Inspection_Date = moment().toDate();
var safe_For_Use = false;
if (this.props.inspection.hasDefects == true) {
next_Inspection_Date = moment().toDate();
safe_For_Use = false;
} else {
next_Inspection_Date = moment(inspected_At).add(this.props.equip.inspection_Interval, 'M').toDate();
safe_For_Use = true;
}
this.setState({inspected_At: inspected_At, next_Inspection_Date: next_Inspection_Date, safe_For_Use: safe_For_Use}, () => {
console.log("NEXT INSPECTION DATE state: " + this.state.next_Inspection_Date);
});
}
// Part of view
<View style={styles.nextInspectionDateContainer}>
<View style={styles.inputContainer}>
<Text style={styles.inputLabel}>Next Inspection Date:</Text>
<DatePicker
locale="en_GB"
defaultDate={this.state.next_Inspection_Date}
formatChosenDate={date => { return moment(date).format('DD/MM/YYYY'); }}
onDateChange={(date) => { this.setState({ next_Inspection_Date: date }) }}
/>
</View>
</View>
This is pretty common (:
this.setState() is async so you will need to return a promise from this.setState() to a function which will update your DatePicker instead of just logging them. Then you can call forceupdate() to show your updates.
if you were using stateless functional components, it will be much better as you would only need a simple Hook to get all the above done.
keep in mind from docs:
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render().
We have a toast component in our app that is adding considerable flakiness to our tests. The toast component displays an animated View for 4s and then disappears. In a lot of tests I need to check what the message content is in order to continue with the test.
The toast component is implemented with the following code:
// #flow
import * as React from "react"
import { StyleSheet, View, Animated, Dimensions, Text } from "react-native"
import type {
TextStyle,
ViewStyle,
} from "react-native/Libraries/StyleSheet/StyleSheet"
import type AnimatedValue from "react-native/Libraries/Animated/src/nodes/AnimatedValue"
import type { CompositeAnimation } from "react-native/Libraries/Animated/src/AnimatedImplementation"
import { AnimationConstants } from "constants/animations"
const styles = StyleSheet.create({
container: {
position: "absolute",
left: 0,
right: 0,
elevation: 999,
alignItems: "center",
zIndex: 10000,
},
content: {
backgroundColor: "black",
borderRadius: 5,
padding: 10,
},
text: {
color: "white",
},
})
type Props = {
style: ViewStyle,
position: "top" | "center" | "bottom",
textStyle: TextStyle,
positionValue: number,
fadeInDuration: number,
fadeOutDuration: number,
opacity: number,
}
type State = {
isShown: boolean,
text: string | React.Node,
opacityValue: AnimatedValue,
}
export const DURATION = AnimationConstants.durationShort
const { height } = Dimensions.get("window")
export default class Toast extends React.PureComponent<Props, State> {
static defaultProps = {
position: "bottom",
textStyle: styles.text,
positionValue: 120,
fadeInDuration: AnimationConstants.fadeInDuration,
fadeOutDuration: AnimationConstants.fadeOutDuration,
opacity: 1,
}
isShown: boolean
duration: number
callback: Function
animation: CompositeAnimation
timer: TimeoutID
constructor(props: Props) {
super(props)
this.state = {
isShown: false,
text: "",
opacityValue: new Animated.Value(this.props.opacity),
}
}
show(text: string | React.Node, duration: number, callback: Function) {
this.duration = typeof duration === "number" ? duration : DURATION
this.callback = callback
this.setState({
isShown: true,
text: text,
})
this.animation = Animated.timing(this.state.opacityValue, {
toValue: this.props.opacity,
duration: this.props.fadeInDuration,
useNativeDriver: true,
})
this.animation.start(() => {
this.isShown = true
this.close()
})
}
close(duration?: number) {
const delay = typeof duration === "undefined" ? this.duration : duration
if (!this.isShown && !this.state.isShown) return
this.timer && clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.animation = Animated.timing(this.state.opacityValue, {
toValue: 0.0,
duration: this.props.fadeOutDuration,
useNativeDriver: true,
})
this.animation.start(() => {
this.setState({
isShown: false,
})
this.isShown = false
if (typeof this.callback === "function") {
this.callback()
}
})
}, delay)
}
componentWillUnmount() {
this.animation && this.animation.stop()
this.timer && clearTimeout(this.timer)
}
render() {
const { isShown, text, opacityValue } = this.state
const { position, positionValue } = this.props
const pos = {
top: positionValue,
center: height / 2,
bottom: height - positionValue,
}[position]
if (isShown) {
return (
<View style={[styles.container, { top: pos }]}>
<Animated.View
style={[
styles.content,
{ opacity: opacityValue },
this.props.style,
]}
>
{React.isValidElement(text) ? (
text
) : (
<Text style={this.props.textStyle}>{text}</Text>
)}
</Animated.View>
</View>
)
}
return null
}
}
Normally we display the toast message for 4s, but I decided to display it in e2e tests for 1.5s in order to make some what faster.
I'm testing for the presence of the toast like this:
await expect(element(by.text(text))).toBeVisible()
await waitFor(element(by.text(text))).toBeNotVisible().withTimeout(2000)
However it happens often that detox fails at "toBeVisible". I can see the message on the screen, but for some reason detox is missing it.
What is the minimum time I should keep the message on the screen for detox to detect it?
On .circleCI we record videos of failing tests. When a test fails with "cannot find element" and I watch the video I clearly see the toast appearing on the screen, but detox fails to find it.
I'm still not sure if there is a better way, but I found a way that currently works for us.
Instead of automatically hiding the toast in e2e test, we mock the modal component to display and stay visible until tapped on.
Once detox sees the element we tap on it, close it and continue with our test.
I also had exactly the same problem in my project and the the solution that we found was to disable detox synchronization around the toast.
As an example, this is how the code would look like:
await device.disableSynchronization();
await element(by.id(showToastButtonId)).tap();
await waitFor(element(by.text('Toast Message')))
.toExist()
.withTimeout(TIMEOUT_MS);
await device.enableSynchronization();
Reference: https://github.com/wix/Detox/blob/master/docs/Troubleshooting.Synchronization.md#switching-to-manual-synchronization-as-a-workaround
I have a button at the middle of my screen. onScroll I want the button to scale down to 0 to disappear and then scale back up to 1 to reappear in a new position at the bottom of the screen. I want to be able call setState (which controls the position of the button) in between the scale down and scale up animations. Something like the code below. Any idea of the best way to add a function call in between these two animations? Or an even better way of doing this?
animateScale = () => {
return (
Animated.sequence([
Animated.timing(
this.state.scale,
{
toValue: 0,
duration: 300
}
),
this.setState({ positionBottom: true }),
Animated.timing(
this.state.scale,
{
toValue: 1,
duration: 300
}
)
]).start()
)
}
After more research I found the answer.start() takes a callback function as shown here:
Calling function after Animate.spring has finished
Here was my final solution:
export default class MyAnimatedScreen extends PureComponent {
state = {
scale: new Animated.Value(1),
positionUp: true,
animating: false,
};
animationStep = (toValue, callback) => () =>
Animated.timing(this.state.scale, {
toValue,
duration: 200,
}).start(callback);
beginAnimation = (value) => {
if (this.state.animating) return;
this.setState(
{ animating: true },
this.animationStep(0, () => {
this.setState(
{ positionUp: value, animating: false },
this.animationStep(1)
);
})
);
};
handleScrollAnim = ({ nativeEvent }) => {
const { y } = nativeEvent.contentOffset;
if (y < 10) {
if (!this.state.positionUp) {
this.beginAnimation(true);
}
} else if (this.state.positionUp) {
this.beginAnimation(false);
}
};
render() {
return (
<View>
<Animated.View
style={[
styles.buttonWrapper,
{ transform: [{ scale: this.state.scale }] },
this.state.positionUp
? styles.buttonAlignTop
: styles.buttonAlignBottom,
]}
>
<ButtonCircle />
</Animated.View>
<ScrollView onScroll={this.handleScrollAnim}>
// scroll stuff here
</ScrollView>
</View>
);
}
}
That is correct answer.
Tested on Android react-native#0.63.2
Animated.sequence([
Animated.timing(someParam, {...}),
{
start: cb => {
//Do something
console.log(`I'm wored!!!`)
cb({ finished: true })
}
},
Animated.timing(someOtherParam, {...}),
]).start();